//*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#*#* DtRecord.cpp *#*#*#*#*#*#*#*# (C) 2000-2025 DekTec
//

//.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Include files -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
#include <stdio.h>
#include "DTAPI.h"
#include "DtRecord.h"
#include "DtSdiFileFmt.h"
#include "SdiFileFmt.h"
#include <chrono>
#include <numeric>

#ifdef WINBUILD
    #define NOMINMAX
    #include <Windows.h>
    #include <conio.h>
#else
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <termios.h>
    #include <arpa/inet.h>
#endif

// namespaces
using namespace std;
using namespace std::chrono;

#ifndef WINBUILD
void ChangeTerminalMode(int dir)
{
    static struct termios  OldT, NewT;

    if (dir == 1)
    {
        tcgetattr(STDIN_FILENO, &OldT);
        NewT = OldT;
        NewT.c_lflag &= ~(ICANON | ECHO);
        tcsetattr(STDIN_FILENO, TCSANOW, &NewT);
    } else {
        tcsetattr(STDIN_FILENO, TCSANOW, &OldT);
    }
}
int  _kbhit()
{
    struct timeval  tv;
    fd_set  rdfs;

    tv.tv_sec = 0;
    tv.tv_usec = 0;

    FD_ZERO(&rdfs);
    FD_SET(STDIN_FILENO, &rdfs);

    select(STDIN_FILENO+1, &rdfs, NULL, NULL, &tv);
    return FD_ISSET(STDIN_FILENO, &rdfs);
}
#endif


// .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- GetTimestamp -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void GetTimestamp(uint32_t& sec, uint32_t& usec)
{
    auto now = system_clock::now();
    auto s = time_point_cast<seconds>(now);
    auto us = duration_cast<microseconds>(now - s);
    sec = (uint32_t)s.time_since_epoch().count();
    usec = (uint32_t)us.count();
}

#define min(a,b) ((a)<(b) ? (a) : (b))

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- DtRecord Version -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
#define DTRECORD_VERSION_MAJOR      4
#define DTRECORD_VERSION_MINOR      16
#define DTRECORD_VERSION_BUGFIX     3

// Command line option flags
const char c_DvcTypeCmdLineFlag[]           = "t";
const char c_DvcNumCmdLineFlag[]            = "n";
const char c_PortCmdLineFlag[]              = "i";

const char c_RxModeCmdLineFlag[]            = "m";
const char c_RxModeHuffmanCmdLineFlag[]     = "c";
const char c_MaxSizeCmdLineFlag[]           = "x";

const char c_ModTypeCmdLineFlag[]           = "mt";
const char c_CarrierFreqCmdLineFlag[]       = "mf";
const char c_QamJ83AnnexCmdLineFlag[]       = "ma";
const char c_ModBandwidthCmdLineFlag[]      = "mb";
const char c_SymbolRateCmdLineFlag[]        = "ms";
const char c_NumberOfSegmentsCmdLineFlag[]  = "mn";
const char c_SubChannelCmdLineFlag[]        = "mc";
const char c_StreamIdCmdLineFlag[]          = "mi";

const char c_IpAddressCmdLineFlag[]         = "ipa";
const char c_IpProtocolCmdLineFlag[]        = "ipp";

const char c_IqDemodTypeCmdLineFlag[]       = "iqd";
const char c_IqBandwidthCmdLineFlag[]       = "iqb";
const char c_IqSampleRateCmdLineFlag[]      = "iqs";

const char c_LnbVoltageCmdLineFlag[]        = "lnbv";
const char c_LnbToneCmdLineFlag[]           = "lnbt";
const char c_LnbBurstCmdLineFlag[]          = "lnbb";

const char c_DiseqcPortGroupCmdLineFlag[]   = "diseqcpg";

const char c_PolarityControlCmdLineFlag[]   = "pc";

const char c_LogMerCmdLineFlag[]            = "mer";
const char c_SilentModeCmdLineFlag[]        = "s";
const char c_HelpCmdLineFlag[]              = "?";

const int c_BufSize = 16*1024*1024;     // Data transfer buffer size

//.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Error messages -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

// Command line errors
const char c_CleUnknownOption[]         = "Unknown command-line option (-%s) encountered";
const char c_CleMissingArgument[]       = "Missing argument for command-line option: -%s";
const char c_CleNoArgumentExpected[]    = "-%s command-line option does not have a argument";
const char c_CleInvalidArgument[]       = "Invalid argument for command line option: -%s";
const char c_CleNoRecFile[]             = "No record file specified";
const char c_CleTxRateNotDefined[]      = "Transport-Stream rate not specified";
const char c_CleDeviceTypeRequired[]    = "Device type must be set if the port is specified";
const char c_CleNoHuffmanForSdi[]       = "No Huffman compression supported for SDI format";
const char c_CleInvalidSdiOptions[]     = "Invalid SDI options";

// General errors
const char c_ErrDtapiHwScanFailed[]     = "DtapiHwFuncScan failed (ERROR: %s)";
const char c_ErrNoInputFound[]          = "No input device in the system";
const char c_ErrNoDta1xxFound[]         = "No DTA-%d with input port %d found";
const char c_ErrNoDtu2xxFound[]         = "No DTU-%d with input port %d found";
const char c_ErrCouldNotFindInputN[]    = "Could not find input board (#%d, port=%d) in the system";
const char c_ErrCouldNotFindDta1xxN[]   = "Could not find DTA-%d (#%d, port=%d) in the system";
const char c_ErrCouldNotFindDtu2xxN[]   = "Could not find DTU-%d (#%d, port=%d) in the system";
const char c_ErrFailToAttachToDta1xx[]  = "Failed to attach to the DTA-%d on Bus: %d and Slot: %d";
const char c_ErrFailToAttachToDtu2xx[]  = "Failed to attach to the DTU-%d";
const char c_ErrFailToAttachToChan[]    = "Can't attach to the channel (ERROR: %s)";
const char c_ErrFailToOpenFile[]        = "Can't open '%s' for writing";
const char c_ErrWriteFile[]             = "File write error";
const char c_ErrFailDetectIoStd[]       = "DetectIoStd failed (ERROR: %s)";
const char c_ErrFailToSetIoConfig[]     = "Failed to set IO-configuration (ERROR: %s)";
const char c_ErrFailSetRxControl[]      = "SetRxControl failed (ERROR: %s)";
const char c_ErrFailSetRxMode[]         = "SetRxMode failed (ERROR: %s)";
const char c_ErrFailSetPolarCtrl[]      = "SetPolarityControl failed (ERROR: %s)";
const char c_ErrFailSetDemodControl[]   = "SetDemodControl failed (ERROR: %s)";
const char c_ErrFailSetRfControl[]      = "SetRfControl failed (ERROR: %s)";
const char c_ErrFailSetIpPars[]         = "SetIpPars failed (ERROR: %s)";
const char c_ErrFailRead[]              = "Read failed (ERROR: %s)";
const char c_ErrFailWriteDtSdiFileHdr[] = "Failed to write DTSDI file header";
const char c_ErrFailWriteSdiFileHdr[]   = "Failed to write SDI file header";
const char c_CleL3RequiresDVBS2[]       = "STL3 Mode only valid for modulation type DVBS2 (-mt DVBS2)";
const char c_ErrFailHwFuncScan[]        = "HwFuncScan failed (ERROR: %s)";
const char c_ErrFailGetDemodStatus[]    = "GetDemodStatus failed (ERROR: %s)";
const char c_ErrFailGetStatistic[]      = "GetStatistic failed (ERROR: %s)";
const char c_ErrFailL3Align[]           = "Can't align to L3 baseband frame";
const char c_ErrSymbolRateNotSet[]      = "Symbol rate must be set for DTU-236 in QAM-A/C and for DTA2132 in DVBS2";
const char c_ErrFailApskPort2[]         = "DVB-S2 16/32APSK is not supported for port 2";
const char c_ErrFailOpenStream[]        = "OpenStream failed (ERROR: %s)";
const char c_ErrFailRegisterCallback[]  = "RegisterCallback failed (ERROR: %s)";
const char c_ErrInvalidVideoStandard[]  = "Invalid video standard";
const char c_ErrInvalidAligment[]       = "Invalid video alignment";


const char c_ErrDriverInCompPci[] =
    "The current Dta1xx driver (V%d.%d.%d %d) is not compatible with this\n" \
    "version of DtRecord.\n" \
    "Please install the latest version of the Dta1xx driver.";

const char c_ErrDriverInCompUsb[] =
    "The current Dtu2xx driver (V%d.%d.%d %d) is not compatible with this\n" \
    "version of DtRecord.\n" \
    "Please install the latest version of the Dtu2xx driver.";

// Warning messages
const char c_WarnHwFifoOverflow[] = 
    "WARNING: hardware-FIFO overflow detected => possible corruption of record file";


//=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ class NwUtils +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+

// .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- NwUtils::ip_checksum2 -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
uint32_t NwUtils::ip_checksum2(const uint8_t* tab, int len, uint32_t sum)
{
    const uint16_t* tab1;
    int n;

    tab1 = (const uint16_t*)tab;
    for (n = len >> 1; n != 0; n--) {
        sum += *tab1++;
    }
    return sum;
}

// .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- NwUtils::ip_checksum -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
// Return the IP checksum in host byte order. tab must be 16 bit
// aligned. This is a modulo 2^16-1 sum, so 0 and 0xffff are
// equivalent. 0xffff never happens in practice except if all the
// bytes are zero or len = 0.
//
uint16_t NwUtils::ip_checksum(const uint8_t* tab, int len, uint32_t sum)
{
    const uint16_t* tab1;
    int n;

    tab1 = (const uint16_t*)tab;
    for (n = len >> 1; n != 0; n--) {
        sum += *tab1++;
    }
    /* last byte */
    if (len & 1)
        sum += ntohs(*(const uint8_t*)tab1 << 8);
    sum = (sum & 0xffff) + (sum >> 16);
    sum = (sum & 0xffff) + (sum >> 16);
    return (~sum) & 0xffff;
}

// .-.-.-.-.-.-.-.-.-.-.-.-.-.-.- NwUtils::add_udp_headers2 -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
int NwUtils::add_udp_headers2(uint8_t* buf, int payload_len, 
    uint32_t saddr, uint16_t sport, uint32_t daddr, uint16_t dport)
{
    IPHeader* iphdr;
    UDPHeader* udphdr;
    int tot_len, udp_len;
    uint32_t sum;

    /* build a dummy RTP packet */
    iphdr = (IPHeader*)buf;
    udphdr = (UDPHeader*)(buf + sizeof(IPHeader));
    memset(iphdr, 0, sizeof(IPHeader));
    iphdr->ihl = 5;
    iphdr->version = 4;
    iphdr->tos = 0;
    tot_len = payload_len + sizeof(IPHeader) + sizeof(UDPHeader);
    iphdr->tot_len = htons(tot_len);
    iphdr->id = 0;
    iphdr->frag_off = 0;
    iphdr->ttl = 255;
    iphdr->protocol = 0x11;
    iphdr->check = 0;
    iphdr->saddr = htonl(saddr);
    iphdr->daddr = htonl(daddr);
    iphdr->check = ip_checksum((const uint8_t*)iphdr, sizeof(IPHeader), 0);
    udphdr->source = htons(sport);
    udphdr->dest = htons(dport);
    udp_len = payload_len + sizeof(UDPHeader);
    udphdr->len = htons(udp_len);
    udphdr->check = 0;

    sum = ip_checksum2((const uint8_t*)&iphdr->saddr, 2 * sizeof(uint32_t), 0);
    /* protocol & udp length */
    sum += udphdr->len + ntohs(0x11);

    udphdr->check = ip_checksum((const uint8_t*)udphdr, udp_len, sum);
    if (udphdr->check == 0)
        udphdr->check = 0xffff;

    return payload_len + sizeof(IPHeader) + sizeof(UDPHeader);
}

//+=+=+=+=+=+=+=+=+=+=+=+=+=+ CommandLineParams implementation +=+=+=+=+=+=+=+=+=+=+=+=+=+

//-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::CommandLineParams -.-.-.-.-.-.-.-.-.-.-.-.-
//
CommandLineParams::CommandLineParams() :
    m_pLastFlag(NULL),
    m_LastFlagReqsArg(false),
    m_Error(false)
{
}

//.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::~CommandLineParams -.-.-.-.-.-.-.-.-.-.-.-.-
//
CommandLineParams::~CommandLineParams()
{
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::Init -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
void CommandLineParams::Init()
{
    m_FileName[0] = '\0';

    m_DvcType = -1;
    m_DvcNum = 1;
    m_Port = 1;

    m_RxMode = -1;
    m_RxModeHuffman = false;
    m_MaxSize = -1;

    m_ModType = -1;
    m_Bandwidth = -1;
    m_CarrierFreq = -1.0;
    m_QamJ83Annex = DTAPI_MOD_J83_A;
    m_SymbolRate = DTAPI_MOD_SYMRATE_AUTO;
    m_NumberOfSegments = DTAPI_ISDBT_SEGM_13;
    m_SubChannelNumber = 22;
    m_StreamId = 0;
    m_LogMer = false;

    m_LnbVoltage = -1;
    m_LnbToneEnable = false;
    m_LnbBurst = -1;
    m_DiseqcPortGroup = -1;

    memset(&m_IpPars, 0, sizeof(m_IpPars));
    // Set default IP parameters
    m_IpPars.m_Port = 5678;
    m_IpPars.m_Protocol = DTAPI_PROTO_AUTO;

    m_IqDemodType = DTAPI_DEMOD_QAM;
    m_IqBandwidth = -1;
    m_IqSampleRate = -1;
    
    m_Polarity = -1;

    m_SilentMode = false;
    m_ShowHelp = false;

    m_RecDtSdiFile = false;
    m_RecSdiFile = false;
}

//-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::ParseCommandLine -.-.-.-.-.-.-.-.-.-.-.-.-.
//
void CommandLineParams::ParseCommandLine(int argc, char* argv[])
{
    bool First = true;
    for ( int i=1; i<argc; i++ )
    {
        char* pParam = argv[i];
        bool Flag = false;
        bool Last = ( (i+1)==argc );
#ifdef WINBUILD
        if ( !m_LastFlagReqsArg && (pParam[0]=='-' || pParam[0] == '/') )
#else
        if ( !m_LastFlagReqsArg && pParam[0]=='-' ) // For Linux only '-' can be used for options
#endif
        {
            // remove flag specifier
            Flag = true;
            ++pParam;
        }
        ParseParam(pParam, Flag, First, Last);
        First = false;
    }

    // Check for required parameters
    if ( strlen(m_FileName) == 0 && !m_ShowHelp )
        throw Exc(c_CleNoRecFile);

    if (m_Port != -1 && m_DvcType == -1 && !m_ShowHelp )
        throw Exc(c_CleDeviceTypeRequired);

    // Not both file options allowed
    if (m_RecDtSdiFile && m_RecSdiFile)
        throw Exc(c_CleInvalidSdiOptions);

    // Check for Huffman compression
    if ( m_RecDtSdiFile && m_RxModeHuffman )
        m_RxMode |= DTAPI_RXMODE_SDI_HUFFMAN;
    else if (m_RecSdiFile && m_RxModeHuffman)
        throw Exc(c_CleNoHuffmanForSdi);

    // Check modulation type when STL3 with/without timestamps is used
    if (m_RxMode==DTAPI_RXMODE_STL3  || 
                           m_RxMode==(DTAPI_RXMODE_STL3 | DTAPI_RXMODE_TIMESTAMP32) || 
                           m_RxMode==DTAPI_RXMODE_STL3FULL  || 
                           m_RxMode==(DTAPI_RXMODE_STL3FULL | DTAPI_RXMODE_TIMESTAMP32) ||
                           m_RxMode==DTAPI_RXMODE_STL3ALL ||
                           m_RxMode==(DTAPI_RXMODE_STL3ALL | DTAPI_RXMODE_TIMESTAMP32))
    {
        if (m_ModType != DTAPI_MOD_DVBS2 && m_ModType != DTAPI_MOD_DVBS2_QPSK 
               && m_ModType != DTAPI_MOD_DVBS2_8PSK && m_ModType != DTAPI_MOD_DVBS2_16APSK 
               && m_ModType != DTAPI_MOD_DVBS2_32APSK)
            throw Exc(c_CleL3RequiresDVBS2, m_pLastFlag);
    }

    //.-.-.-.-.-.-.-.-.-.-.-.-.-.- Set dynamic option default -.-.-.-.-.-.-.-.-.-.-.-.-.-.
    //
    // Set default settings for parameters which are not defined on the command line

    // RxMode defined?
    if (m_RxMode == -1)
    {
        m_RxMode = DTAPI_RXMODE_ST188;
   
        if ( m_ModType == DTAPI_MOD_ISDBT ) 
            m_RxMode = DTAPI_RXMODE_ST204;
    }

    // Carrier defined?
    if (m_CarrierFreq<0.0 && m_ModType!=DTAPI_MOD_IQDIRECT)
    {
        //if ( m_ModType==DTAPI_MOD_QPSK || m_ModType==DTAPI_MOD_BPSK )
        //  m_CarrierFreq = 1915.0;     // L-Band
        //else /* ISDBT, OFDM, QAM */
            m_CarrierFreq = 578.0;      // UHF-band
    }
    
    if ( m_Polarity == -1)
    {
        if ( m_RxMode == DTAPI_RXMODE_STRAW )
            m_Polarity = DTAPI_POLARITY_NORMAL;
        else
            m_Polarity = DTAPI_POLARITY_AUTO;
    }

    if ( m_Bandwidth == -1 )
    {
        if ( m_ModType == DTAPI_MOD_ATSC3 )
            m_Bandwidth = DTAPI_ATSC3_6MHZ;
        else if ( m_ModType == DTAPI_MOD_DVBT )
            m_Bandwidth = DTAPI_MOD_DVBT_8MHZ;
        else if ( m_ModType == DTAPI_MOD_DVBT2 )
            m_Bandwidth = DTAPI_DVBT2_8MHZ;
        else if ( m_ModType == DTAPI_MOD_DVBC2 )
            m_Bandwidth = DTAPI_DVBC2_8MHZ;
        else if ( m_ModType == DTAPI_MOD_ISDBT )
            m_Bandwidth = DTAPI_ISDBT_BW_6MHZ;
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::ParseParam -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void CommandLineParams::ParseParam(
    char* pParam,
    bool Flag,
    bool First,
    bool Last)
{

    if ( Flag )
        ParseParamFlag(pParam, First, Last);
    else
        ParseParamNotFlag(pParam, First, Last);
}

//.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::ParseParamFlag -.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void  CommandLineParams::ParseParamFlag(char* pParam, bool First, bool Last)
{
    // Did we expect an option??
    if ( m_LastFlagReqsArg && m_pLastFlag != NULL )
        throw Exc(c_CleMissingArgument, m_pLastFlag);

    if ( 0==strcmp(pParam, c_DvcTypeCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_DvcNumCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_PortCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_RxModeCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_RxModeHuffmanCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_ModTypeCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_CarrierFreqCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_QamJ83AnnexCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_SymbolRateCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_NumberOfSegmentsCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_SubChannelCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_StreamIdCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_ModBandwidthCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_LnbVoltageCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_LnbToneCmdLineFlag) )
    {
        m_LastFlagReqsArg = false;
        m_LnbToneEnable = true;
    }
    else if (0 == strcmp(pParam, c_LnbBurstCmdLineFlag))
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_DiseqcPortGroupCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_LogMerCmdLineFlag))
    {
        m_LogMer = true;
        m_LastFlagReqsArg = false;
    }
    else if ( 0==strcmp(pParam, c_MaxSizeCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_IpAddressCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_IpProtocolCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_IqDemodTypeCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_IqBandwidthCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_IqSampleRateCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_PolarityControlCmdLineFlag) )
        m_LastFlagReqsArg = true;
    else if ( 0==strcmp(pParam, c_SilentModeCmdLineFlag))
    {
        m_SilentMode = true;
        m_LastFlagReqsArg = false;
    }
    else if ( 0==strcmp(pParam, c_HelpCmdLineFlag))
    {
        m_ShowHelp = true;
        m_LastFlagReqsArg = false;
    }
    else
        throw Exc(c_CleUnknownOption, pParam);

    m_pLastFlag = pParam;
}

//-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::ParseParamNotFlag -.-.-.-.-.-.-.-.-.-.-.-.-
//
void  CommandLineParams::ParseParamNotFlag(char* pParam, bool First, bool Last)
{
    // First option should be our file name
    if (First)
    {
        strcpy(m_FileName, pParam);
        m_pLastFlag = NULL;
        m_LastFlagReqsArg = false;
        return;
    }

    // Did we expect and option??
    if (m_pLastFlag == NULL)
        return;
    if (!m_LastFlagReqsArg)
        throw Exc(c_CleNoArgumentExpected, m_pLastFlag);

    if (0 == strcmp(m_pLastFlag, c_DvcTypeCmdLineFlag))
    {
        m_DvcType = atoi(pParam);
        if (m_DvcType < 100)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_DvcNumCmdLineFlag))
    {
        m_DvcNum = atoi(pParam);
        if (m_DvcNum < 1)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_PortCmdLineFlag))
    {
        m_Port = atoi(pParam);
        if (m_Port < 1)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_RxModeCmdLineFlag))
    {
        // Which rx-mode
        if (0 == strcmp(pParam, "ST188"))
            m_RxMode = DTAPI_RXMODE_ST188;
        else if (0 == strcmp(pParam, "ST204"))
            m_RxMode = DTAPI_RXMODE_ST204;
        else if (0 == strcmp(pParam, "STRAW"))
            m_RxMode = DTAPI_RXMODE_STRAW;
        else if (0 == strcmp(pParam, "STL3TS"))
            m_RxMode = DTAPI_RXMODE_STL3 | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "STL3"))
            m_RxMode = DTAPI_RXMODE_STL3;
        else if (0 == strcmp(pParam, "STL3FULLTS"))
            m_RxMode = DTAPI_RXMODE_STL3FULL | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "STL3FULL"))
            m_RxMode = DTAPI_RXMODE_STL3FULL;
        else if (0 == strcmp(pParam, "STL3ALLTS"))
            m_RxMode = DTAPI_RXMODE_STL3ALL | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "STL3ALL"))
            m_RxMode = DTAPI_RXMODE_STL3ALL;
        else if (0 == strcmp(pParam, "STTRP"))
            m_RxMode = DTAPI_RXMODE_STTRP | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "ST188T"))
            m_RxMode = DTAPI_RXMODE_ST188 | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "ST204T"))
            m_RxMode = DTAPI_RXMODE_ST204 | DTAPI_RXMODE_TIMESTAMP32;
        else if (0 == strcmp(pParam, "DTSDI10B"))
        { 
            m_RxMode = DTAPI_RXMODE_SDI_FULL | DTAPI_RXMODE_SDI_10B;
            m_RecDtSdiFile = true;
        }
        else if (0 == strcmp(pParam, "DTSDI16B"))
        { 
            m_RxMode = DTAPI_RXMODE_SDI_FULL | DTAPI_RXMODE_SDI_16B;
            m_RecDtSdiFile = true;
        }
        else if (0 == strcmp(pParam, "SDI10B"))
        { 
            m_RxMode = DTAPI_RXMODE_SDI_FULL | DTAPI_RXMODE_SDI_10B;
            m_RecSdiFile = true;
        }
        else if (0 == strcmp(pParam, "SDI16B"))
        { 
            m_RxMode = DTAPI_RXMODE_SDI_FULL | DTAPI_RXMODE_SDI_16B;
            m_RecSdiFile = true;
        }
        else if (0 == strcmp(pParam, "RAWASI"))
            m_RxMode = DTAPI_RXMODE_RAWASI;
        else  if (0 == strcmp(pParam, "STMP2"))
            m_RxMode = DTAPI_RXMODE_STMP2;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_RxModeHuffmanCmdLineFlag))
    {
        if (0 == strcmp(pParam, "ON"))
            m_RxModeHuffman = true;
        else if (0 == strcmp(pParam, "OFF"))
            m_RxModeHuffman = false;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_ModTypeCmdLineFlag))
    {
        // Which modulation mode
        if (0 == strcmp(pParam, "QAM4"))
            m_ModType = DTAPI_MOD_QAM4;
        else if (0 == strcmp(pParam, "QAM16"))
            m_ModType = DTAPI_MOD_QAM16;
        else if (0 == strcmp(pParam, "QAM32"))
            m_ModType = DTAPI_MOD_QAM32;
        else if (0 == strcmp(pParam, "QAM64"))
            m_ModType = DTAPI_MOD_QAM64;
        else if (0 == strcmp(pParam, "QAM128"))
            m_ModType = DTAPI_MOD_QAM128;
        else if (0 == strcmp(pParam, "QAM256"))
            m_ModType = DTAPI_MOD_QAM256;
        else if (0 == strcmp(pParam, "VSB8"))
            m_ModType = DTAPI_MOD_ATSC;
        else if (0 == strcmp(pParam, "ATSC"))
            m_ModType = DTAPI_MOD_ATSC;
        else if (0 == strcmp(pParam, "ATSC3"))
            m_ModType = DTAPI_MOD_ATSC3;
        else if (0 == strcmp(pParam, "DAB"))
        {
            m_ModType = DTAPI_MOD_DAB;
            m_RxMode = DTAPI_RXMODE_STRAW;
        }
        else if (0 == strcmp(pParam, "DVBT"))
            m_ModType = DTAPI_MOD_DVBT;
        else if (0 == strcmp(pParam, "DVBT2"))
            m_ModType = DTAPI_MOD_DVBT2;
        else if (0 == strcmp(pParam, "DVBC2"))
            m_ModType = DTAPI_MOD_DVBC2;
        else if (0 == strcmp(pParam, "ISDBT"))
            m_ModType = DTAPI_MOD_ISDBT;
        else if (0 == strcmp(pParam, "DVBS"))
            m_ModType = DTAPI_MOD_DVBS_QPSK; // or DTAPI_MOD_DVBS_BPSK
        else if (0 == strcmp(pParam, "DVBS2"))
            m_ModType = DTAPI_MOD_DVBS2;
        else if (0 == strcmp(pParam, "DVBS2_QPSK"))
            m_ModType = DTAPI_MOD_DVBS2_QPSK;
        else if (0 == strcmp(pParam, "DVBS2_8PSK"))
            m_ModType = DTAPI_MOD_DVBS2_8PSK;
        else if (0 == strcmp(pParam, "DVBS2_16APSK"))
            m_ModType = DTAPI_MOD_DVBS2_16APSK;
        else if (0 == strcmp(pParam, "DVBS2_32APSK"))
            m_ModType = DTAPI_MOD_DVBS2_32APSK;
        else if (0 == strcmp(pParam, "IQ"))
            m_ModType = DTAPI_MOD_IQDIRECT;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_CarrierFreqCmdLineFlag))
    {
        m_CarrierFreq = atof(pParam);
        if (m_CarrierFreq < 0.0)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_QamJ83AnnexCmdLineFlag))
    {
        if (0 == strcmp(pParam, "A"))
            m_QamJ83Annex = DTAPI_MOD_J83_A;
        else if (0 == strcmp(pParam, "B"))
            m_QamJ83Annex = DTAPI_MOD_J83_B;
        else if (0 == strcmp(pParam, "C"))
            m_QamJ83Annex = DTAPI_MOD_J83_C;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_SymbolRateCmdLineFlag))
    {
        m_SymbolRate = atoi(pParam);
        if (m_SymbolRate<500000 || m_SymbolRate>72000000)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    } 
    else if (0 == strcmp(m_pLastFlag, c_NumberOfSegmentsCmdLineFlag))
    {
        m_NumberOfSegments = atoi(pParam);
        if (m_NumberOfSegments!=DTAPI_ISDBT_SEGM_13 && 
            m_NumberOfSegments!=DTAPI_ISDBT_SEGM_3 &&
            m_NumberOfSegments!=DTAPI_ISDBT_SEGM_1)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_SubChannelCmdLineFlag))
    {
        m_SubChannelNumber = atoi(pParam);
        if (m_SubChannelNumber<0 || m_SubChannelNumber>41)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_StreamIdCmdLineFlag))
    {
        m_StreamId = atoi(pParam);
        if (m_StreamId<-1 || m_StreamId>255)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_ModBandwidthCmdLineFlag))
    {
        // Modulation type should be defined first
        if (m_ModType == DTAPI_MOD_ATSC3)
        {
            if (0 == strcmp(pParam, "6"))
                m_Bandwidth = DTAPI_ATSC3_6MHZ;
            else if (0 == strcmp(pParam, "7"))
                m_Bandwidth = DTAPI_ATSC3_7MHZ;
            else if (0 == strcmp(pParam, "8"))
                m_Bandwidth = DTAPI_ATSC3_8MHZ;
            else
                throw Exc(c_CleInvalidArgument, m_pLastFlag);
        }
        else if (m_ModType == DTAPI_MOD_DVBT)
        {
            if (0 == strcmp(pParam, "6"))
                m_Bandwidth = DTAPI_MOD_DVBT_6MHZ;
            else if (0 == strcmp(pParam, "7"))
                m_Bandwidth = DTAPI_MOD_DVBT_7MHZ;
            else if (0 == strcmp(pParam, "8"))
                m_Bandwidth = DTAPI_MOD_DVBT_8MHZ;
            else
                throw Exc(c_CleInvalidArgument, m_pLastFlag);
        }
        else if (m_ModType == DTAPI_MOD_DVBT2)
        {
            if (0 == strcmp(pParam, "1"))
                m_Bandwidth = DTAPI_DVBT2_1_7MHZ;
            else if (0 == strcmp(pParam, "5"))
                m_Bandwidth = DTAPI_DVBT2_5MHZ;
            else if (0 == strcmp(pParam, "6"))
                m_Bandwidth = DTAPI_DVBT2_6MHZ;
            else if (0 == strcmp(pParam, "7"))
                m_Bandwidth = DTAPI_DVBT2_7MHZ;
            else if (0 == strcmp(pParam, "8"))
                m_Bandwidth = DTAPI_DVBT2_8MHZ;
            else if (0 == strcmp(pParam, "10"))
                m_Bandwidth = DTAPI_DVBT2_10MHZ;
            else
                throw Exc(c_CleInvalidArgument, m_pLastFlag);
        }
        else if (m_ModType == DTAPI_MOD_DVBC2)
        {
            if (0 == strcmp(pParam, "6"))
                m_Bandwidth = DTAPI_DVBC2_6MHZ;
            else if (0 == strcmp(pParam, "8"))
                m_Bandwidth = DTAPI_DVBC2_8MHZ;
            else
                throw Exc(c_CleInvalidArgument, m_pLastFlag);
        }
        else if (m_ModType == DTAPI_MOD_ISDBT)
        {
            if (0 == strcmp(pParam, "5"))
                m_Bandwidth = DTAPI_ISDBT_BW_5MHZ;
            else if (0 == strcmp(pParam, "6"))
                m_Bandwidth = DTAPI_ISDBT_BW_6MHZ;
            else if (0 == strcmp(pParam, "7"))
                m_Bandwidth = DTAPI_ISDBT_BW_7MHZ;
            else if (0 == strcmp(pParam, "8"))
                m_Bandwidth = DTAPI_ISDBT_BW_8MHZ;
            else
                throw Exc(c_CleInvalidArgument, m_pLastFlag);
        }
    }
    else if (0 == strcmp(m_pLastFlag, c_LnbVoltageCmdLineFlag))
    {
        m_LnbVoltage = atoi(pParam);
        if (m_LnbVoltage == 13)
            m_LnbVoltage = DTAPI_LNB_13V;
        else if (m_LnbVoltage == 14)
            m_LnbVoltage = DTAPI_LNB_14V;
        else if (m_LnbVoltage == 18)
            m_LnbVoltage = DTAPI_LNB_18V;
        else if (m_LnbVoltage == 19)
            m_LnbVoltage = DTAPI_LNB_19V;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_DiseqcPortGroupCmdLineFlag))
    {
        m_DiseqcPortGroup = (int)strtol(pParam, NULL, 16);
        if (m_DiseqcPortGroup < 0xF0 || m_DiseqcPortGroup>0xFF)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_LnbBurstCmdLineFlag))
    {
        if (0 == strcmp(pParam, "A"))
            m_LnbBurst = DTAPI_LNB_BURST_A;
        else if (0 == strcmp(pParam, "B"))
            m_LnbBurst = DTAPI_LNB_BURST_B;
        else 
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_MaxSizeCmdLineFlag))
    {
        m_MaxSize = atoi(pParam);
        if (m_MaxSize < 0)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_IpAddressCmdLineFlag))
    {
        char* p2 = NULL;
        
        // Look for possible IPv6 address with quotes
        char* start = strchr(pParam, '\''); // Find first quote
        char* end = strchr(pParam+1, '\'');  // Find second quote
        if (start && !end || start!=pParam)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
        else if (start && end)
        {
            // Look for optional IP port after the quotes, located after the semicolon
            p2 = strrchr(end+1, ':');
            // Remove quotes
            *end = '\0';
            pParam = start + 1;
        } else // Look for optional IP port, located after the semicolon
            p2 = strrchr(pParam, ':');
        if (p2 != NULL)
        {
            int IpPort = atoi((p2+1));
            *p2 = '\0'; // Insert NULL character at semicolon

            if (IpPort < 0 || IpPort>65535)
                throw Exc(c_CleInvalidArgument, m_pLastFlag);

            m_IpPars.m_Port = (unsigned short)IpPort;
        }
        // Store IP address
        DTAPI_RESULT dr = ::DtapiInitDtTsIpParsFromIpString(m_IpPars, pParam, NULL);
        if (dr != DTAPI_OK)
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_IpProtocolCmdLineFlag))
    {
        if (0 == strcmp(pParam, "AUTO") )
            m_IpPars.m_Protocol = DTAPI_PROTO_AUTO;
        else if ( 0 == strcmp(pParam, "UDP")  )
            m_IpPars.m_Protocol = DTAPI_PROTO_UDP;          
        else if ( 0 == strcmp(pParam, "RTP")  )
            m_IpPars.m_Protocol = DTAPI_PROTO_RTP;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_IqDemodTypeCmdLineFlag))
    {
        if (0 == strcmp(pParam, "QAM") )
            m_IqDemodType = DTAPI_DEMOD_QAM;
        else if ( 0 == strcmp(pParam, "OFDM")  )
            m_IqDemodType = DTAPI_DEMOD_OFDM;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else if (0 == strcmp(m_pLastFlag, c_IqBandwidthCmdLineFlag))
    {
        m_IqBandwidth = atoi(pParam);
    }
    else if (0 == strcmp(m_pLastFlag, c_IqSampleRateCmdLineFlag))
    {
        m_IqSampleRate = atoi(pParam);
    }
    else if (0 == strcmp(m_pLastFlag, c_PolarityControlCmdLineFlag))
    {
        if (0 == strcmp(pParam, "NORMAL") )
            m_Polarity = DTAPI_POLARITY_NORMAL;
        else if (0 == strcmp(pParam, "INVERT") )
            m_Polarity = DTAPI_POLARITY_INVERT;
        else if (0 == strcmp(pParam, "AUTO") )
            m_Polarity = DTAPI_POLARITY_AUTO;
        else
            throw Exc(c_CleInvalidArgument, m_pLastFlag);
    }
    else
        throw Exc(c_CleUnknownOption, m_pLastFlag);

    // Clear required argument flag
    m_LastFlagReqsArg = false;
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::RxMode2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::RxMode2Str() const
{
    static char sz[256] = "?";
    if (m_RxMode & DTAPI_RXMODE_SDI)
    {
        if ((m_RxMode & DTAPI_RXMODE_SDI_MASK) == DTAPI_RXMODE_SDI_FULL)
        {
            if (0 != (m_RxMode & DTAPI_RXMODE_SDI_10B))
                m_RecDtSdiFile ? strcpy(sz, "DTSDI10B") : strcpy(sz, "SDI10B");
            else if (0 != (m_RxMode & DTAPI_RXMODE_SDI_16B))
                m_RecDtSdiFile ? strcpy(sz, "DTSDI16B") : strcpy(sz, "SDI16B");
            else
                return "?";
        }
        else
            return "?";

        // Check for Huffman compression
        if (DTAPI_RXMODE_SDI_HUFFMAN == (m_RxMode & DTAPI_RXMODE_SDI_HUFFMAN))
            strcat(sz, " (Huffman compressed)");

        return sz;
    }
    else
    {
        switch (m_RxMode & DTAPI_RXMODE_TS_MASK)
        {
        case DTAPI_RXMODE_ST188:    
            return (m_RxMode & DTAPI_RXMODE_TIMESTAMP32)==0 ? "ST188" : "ST188T";
        case DTAPI_RXMODE_ST204:
            return (m_RxMode & DTAPI_RXMODE_TIMESTAMP32)==0 ? "ST204" : "ST204T";
        case DTAPI_RXMODE_STRAW:            return "STRAW";
        case DTAPI_RXMODE_STL3:             return "STL3";
        case DTAPI_RXMODE_STL3FULL:         return "STL3FULL";
        case DTAPI_RXMODE_STL3ALL:          return "STL3ALL";
        case DTAPI_RXMODE_STTRP:            return "STTRP";
        case DTAPI_RXMODE_STMP2:            return "STMP2";
        case DTAPI_RXMODE_RAWASI:           return "RAWASI";
        default:                            return "?";
        }
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::ModType2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
const char* CommandLineParams::ModType2Str() const
{
    switch( m_ModType )
    {
    case DTAPI_MOD_QAM4:         return "QAM4";
    case DTAPI_MOD_QAM16:        return "QAM16";
    case DTAPI_MOD_QAM32:        return "QAM32";
    case DTAPI_MOD_QAM64:        return "QAM64";
    case DTAPI_MOD_QAM128:       return "QAM128";
    case DTAPI_MOD_QAM256:       return "QAM256";
    case DTAPI_MOD_ATSC:         return "ATSC";
    case DTAPI_MOD_ATSC3:        return "ATSC 3.0";
    case DTAPI_MOD_DAB:          return "DAB(+)";
    case DTAPI_MOD_DVBT:         return "DVB-T";
    case DTAPI_MOD_DVBT2:        return "DVB-T2";
    case DTAPI_MOD_DVBC2:        return "DVB-C2";
    case DTAPI_MOD_DVBS_QPSK:
    case DTAPI_MOD_DVBS_BPSK:    return "DVB-S";
    case DTAPI_MOD_DVBS2:
    case DTAPI_MOD_DVBS2_QPSK:
    case DTAPI_MOD_DVBS2_8PSK:  
    case DTAPI_MOD_DVBS2_16APSK:  
    case DTAPI_MOD_DVBS2_32APSK: return "DVB-S2";
    case DTAPI_MOD_IQDIRECT:     return "IQ";
    case DTAPI_MOD_ISDBT:        return "ISDB-T";
    default:                     return "?";
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::QamJ83Annex2Str -.-.-.-.-.-.-.-.-.-.-.-.-.
//
const char* CommandLineParams::QamJ83Annex2Str() const
{
    switch( m_QamJ83Annex )
    {
    case DTAPI_MOD_J83_A:       return "J.83 Annex A";
    case DTAPI_MOD_J83_B:       return "J.83 Annex B";
    case DTAPI_MOD_J83_C:       return "J.83 Annex C";
    default:                    return "?";
    }
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::Bandwidth2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::Bandwidth2Str() const
{
    switch( m_Bandwidth )
    {
    case DTAPI_MOD_DVBT_6MHZ:   return "6MHz";
    case DTAPI_MOD_DVBT_7MHZ:   return "7MHz";
    case DTAPI_MOD_DVBT_8MHZ:   return "8MHz";
    default:                    return "?";
    }
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::IqDemodType2Str -.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::IqDemodType2Str() const
{
    switch( m_IqDemodType )
    {
    case DTAPI_DEMOD_QAM:       return "QAM";
    case DTAPI_DEMOD_OFDM:      return "OFDM";
    default:                    return "?";
    }
}
//.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::IpProtocol2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::IpProtocol2Str() const
{
    switch ( m_IpPars.m_Protocol )
    {
    case DTAPI_PROTO_AUTO:      return "AUTO";
    case DTAPI_PROTO_UDP:       return "UDP";
    case DTAPI_PROTO_RTP:       return "RTP";
    default:                    return "?";
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::LnbVoltage2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::LnbVoltage2Str() const
{
    switch ( m_LnbVoltage )
    {
    case DTAPI_LNB_13V: return "13V";
    case DTAPI_LNB_14V: return "14V";
    case DTAPI_LNB_18V: return "18V";
    case DTAPI_LNB_19V: return "19V";
    default:            return "Disabled";
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::LnbTone2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
const char* CommandLineParams::LnbTone2Str() const
{
    if (m_LnbToneEnable)
        return "22 kHz";
    else
        return "No Tone";
}

// -.-.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::LnbBurst2Str -.-.-.-.-.-.-.-.-.-.-.-.-.-
//
const char* CommandLineParams::LnbBurst2Str() const
{
    switch (m_LnbBurst)
    {
    case DTAPI_LNB_BURST_A: return "A";
    case DTAPI_LNB_BURST_B: return "B";
    default: return "No burst";
    };
}
//.-.-.-.-.-.-.-.-.-.-.-.- CommandLineParams::DiseqcPortGroup2Str -.-.-.-.-.-.-.-.-.-.-.-.
//
const char* CommandLineParams::DiseqcPortGroup2Str() const
{
    switch ( m_DiseqcPortGroup )
    {
    case 0xF0:  return "OptA, PosA, V, Lo";
    case 0xF1:  return "OptA, PosA, V, Hi";
    case 0xF2:  return "OptA, PosA, H, Lo";
    case 0xF3:  return "OptA, PosA, H, Hi";
    case 0xF4:  return "OptA, PosB, V, Lo";
    case 0xF5:  return "OptA, PosB, V, Hi";
    case 0xF6:  return "OptA, PosB, H, Lo";
    case 0xF7:  return "OptA, PosB, H, Hi";
    case 0xF8:  return "OptB, PosA, V, Lo";
    case 0xF9:  return "OptB, PosA, V, Hi";
    case 0xFA:  return "OptB, PosA, H, Lo";
    case 0xFB:  return "OptB, PosA, H, Hi";
    case 0xFC:  return "OptB, PosB, V, Lo";
    case 0xFD:  return "OptB, PosB, V, Hi";
    case 0xFE:  return "OptB, PosB, H, Lo";
    case 0xFF:  return "OptB, PosB, H, Hi";
    default:    return "???";
    }
}

//+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Recorder implementation +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=

//.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::Recorder -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
Recorder::Recorder() : m_pFile(NULL), m_pBuf(NULL), m_Demod(false),  m_Ip(false), 
                                             m_IsUsingAdvDemod(false),  m_IqMonitor(false)
{
#ifndef WINBUILD
    ChangeTerminalMode(1);
#endif
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::~Recorder -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
Recorder::~Recorder()
{
    // Detach from channel and device
    //m_DtInp.SetRxControl(DTAPI_RXCTRL_IDLE);
    //m_DtInp.Detach(DTAPI_INSTANT_DETACH);
    //m_DtDvc.Detach();

    // Free our buffer
    if ( m_pBuf != NULL )
        delete [] m_pBuf;

    // Do not forget to close our file
    if ( m_pFile != NULL )
        ::fclose(m_pFile);

#ifndef WINBUILD
    ChangeTerminalMode(0);
#endif
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::AttachToInput -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
void Recorder::AttachToInput()
{
    DTAPI_RESULT dr;
    int n, NumHwFuncsFound(0);
    DtHwFuncDesc HwFuncs[40];
    
    // Scan for available DekTec devices
    dr = ::DtapiHwFuncScan(sizeof(HwFuncs)/sizeof(HwFuncs[0]), NumHwFuncsFound, HwFuncs);
    if ( dr != DTAPI_OK )
        throw Exc(c_ErrDtapiHwScanFailed, ::DtapiResult2Str(dr) );

    // Loop through found hardware functions
    bool Found(false), SetIoConfig(false);
    int DeviceNum(0);
    DtHwFuncDesc* pHwf = &HwFuncs[0];
    for ( n=0; n<NumHwFuncsFound; n++, pHwf++ )
    {
        // Skip non-inputs
        if ( 0==(pHwf->m_ChanType & DTAPI_CHAN_INPUT) 
                                                 && (pHwf->m_Flags & DTAPI_CAP_INPUT)==0 )
            continue;

        // Looking for a specific type??
        if (    pHwf->m_DvcDesc.m_TypeNumber == m_CmdLineParams.m_DvcType
             && pHwf->m_Port==m_CmdLineParams.m_Port )
            DeviceNum++;    // only count devices matching our search type
        if (    pHwf->m_DvcDesc.m_TypeNumber == m_CmdLineParams.m_DvcType
             && m_CmdLineParams.m_Port==-1 )
            DeviceNum++;    // Only look for type-number, not port
        else if ( m_CmdLineParams.m_DvcType == -1 )
            DeviceNum++;    // count every device

        if ( DeviceNum==m_CmdLineParams.m_DvcNum )
        {
            Found = true;
            if ( 0==(pHwf->m_ChanType & DTAPI_CHAN_INPUT) )
                SetIoConfig = true;
            break;
        }
    }
    // Did we find our device?
    if ( !Found && DeviceNum==0 )
    {
        // Could not find any matching input at all
        if (   (m_CmdLineParams.m_DvcType>=100 && m_CmdLineParams.m_DvcType<200)
            || (m_CmdLineParams.m_DvcType>=2100 && m_CmdLineParams.m_DvcType<2200) )
            throw Exc(c_ErrNoDta1xxFound, m_CmdLineParams.m_DvcType,
                  m_CmdLineParams.m_Port );
        else if (   (m_CmdLineParams.m_DvcType>=200 && m_CmdLineParams.m_DvcType<300) )
            throw Exc(c_ErrNoDtu2xxFound, m_CmdLineParams.m_DvcType,
                  m_CmdLineParams.m_Port );
        else
            throw Exc(c_ErrNoInputFound);
    }
    else if ( !Found && m_CmdLineParams.m_DvcNum > DeviceNum )
    {
        // Could no find the Nth matching input
        if (   (m_CmdLineParams.m_DvcType>=100 && m_CmdLineParams.m_DvcType<200)
            || (m_CmdLineParams.m_DvcType>=2100 && m_CmdLineParams.m_DvcType<2200) )
            throw Exc(c_ErrCouldNotFindDta1xxN, m_CmdLineParams.m_DvcType,
                      m_CmdLineParams.m_DvcNum, m_CmdLineParams.m_Port);
        else if (   (m_CmdLineParams.m_DvcType>=200 && m_CmdLineParams.m_DvcType<300) )
            throw Exc(c_ErrCouldNotFindDtu2xxN, m_CmdLineParams.m_DvcType,
                      m_CmdLineParams.m_DvcNum, m_CmdLineParams.m_Port);
        else
            throw Exc(c_ErrCouldNotFindInputN, m_CmdLineParams.m_DvcNum,
                      m_CmdLineParams.m_Port);
    }

    // Attach to the device
    dr = m_DtDvc.AttachToSerial(pHwf->m_DvcDesc.m_Serial);
    if ( dr == DTAPI_E_DRIVER_INCOMP )
    {
        // Special case: driver version is not compatible with this version of DtRecord,
        // get the driver version and throw an exception
        int DriverVersionMajor(-1), DriverVersionMinor(-1), DriverVersionBugFix(-1),
            DriverVersionBuild(-1);

        // Get device driver version
        ::DtapiGetDeviceDriverVersion(pHwf->m_DvcDesc.m_Category,
                                      DriverVersionMajor, DriverVersionMinor,
                                      DriverVersionBugFix, DriverVersionBuild);

        // PCI or USB device??
        if ( pHwf->m_DvcDesc.m_Category == DTAPI_CAT_PCI )
        {
            throw Exc( c_ErrDriverInCompPci, DriverVersionMajor, DriverVersionMinor,
                       DriverVersionBugFix, DriverVersionBuild );
        }
        else if ( pHwf->m_DvcDesc.m_Category == DTAPI_CAT_USB )
        {
            throw Exc( c_ErrDriverInCompUsb, DriverVersionMajor, DriverVersionMinor,
                       DriverVersionBugFix, DriverVersionBuild );
        }
    }
    else if ( dr != DTAPI_OK )
    {
        // PCI or USB device??
        if ( pHwf->m_DvcDesc.m_Category == DTAPI_CAT_PCI )
            throw Exc(c_ErrFailToAttachToDta1xx, pHwf->m_DvcDesc.m_TypeNumber,
                      pHwf->m_DvcDesc.m_PciBusNumber, pHwf->m_DvcDesc.m_SlotNumber);
        else
            throw Exc(c_ErrFailToAttachToDtu2xx, pHwf->m_DvcDesc.m_TypeNumber);
    }

    // Set APSK mode for DTA-2137 when modulation standard is DVB-S2 16APSK/32APSK
    // Port 2 must be disabled in order to enable APSK mode for port 1
    if ( m_DtDvc.m_DvcDesc.m_TypeNumber == 2137 )
    {
        if ( pHwf->m_Port == 2 && (m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_16APSK
                                 || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_32APSK ))
            // DVB-S2 16APSK/32APSK is not supported for port 2
            throw Exc(c_ErrFailApskPort2);
        else if ( m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_16APSK
                                  || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_32APSK )
        {
            // Set port 4 as output (disable the loop-through of port 2),
            // otherwise port 2 can be disabled
            dr = m_DtDvc.SetIoConfig(4, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_OUTPUT, 
                                                                   DTAPI_IOCONFIG_OUTPUT); 
            if ( dr != DTAPI_OK )
                throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));

            // Disable port 2
            dr = m_DtDvc.SetIoConfig(2, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_DISABLED); 
            if ( dr != DTAPI_OK )
                throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));

            // Enable APSK mode for port 1
            dr = m_DtDvc.SetIoConfig(1, DTAPI_IOCONFIG_SWS2APSK, DTAPI_IOCONFIG_TRUE); 
            if ( dr != DTAPI_OK )
                throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));
        }
    }

    // Disable APSK mode for DTA-2137 when modulation standard is not DVB-S2 16APSK/32APSK
    // Port 2 can be re-enabled
    if ( m_DtDvc.m_DvcDesc.m_TypeNumber == 2137 
                                  && m_CmdLineParams.m_ModType != DTAPI_MOD_DVBS2_16APSK
                                  && m_CmdLineParams.m_ModType != DTAPI_MOD_DVBS2_32APSK )
    {
        // Disable APSK mode for port 1
        dr = m_DtDvc.SetIoConfig(1, DTAPI_IOCONFIG_SWS2APSK, DTAPI_IOCONFIG_FALSE); 
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));

        // Enable port 2
        dr = m_DtDvc.SetIoConfig(2, DTAPI_IOCONFIG_IODIR, DTAPI_IOCONFIG_INPUT, 
                                                                    DTAPI_IOCONFIG_INPUT); 
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));
    }

    // Only one port can be enabled for DTU-236 and DTU-238. Disable the other port.
    if (m_DtDvc.m_DvcDesc.m_TypeNumber == 236 || m_DtDvc.m_DvcDesc.m_TypeNumber == 238)
    {
        dr = m_DtDvc.SetIoConfig( (pHwf->m_Port==1) ? 2 : 1, DTAPI_IOCONFIG_IODIR,
                                                             DTAPI_IOCONFIG_DISABLED, -1); 
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));
    }

    // Set the IO-config to input?
    if ( SetIoConfig )
    {
        dr = m_DtDvc.SetIoConfig(pHwf->m_Port, DTAPI_IOCONFIG_IODIR, 
                                              DTAPI_IOCONFIG_INPUT, DTAPI_IOCONFIG_INPUT);
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr));
    }

    DtCaps HwCaps;
    // ATSC 3.0 uses the advanced demodulator
    m_IsUsingAdvDemod = (m_CmdLineParams.m_ModType == DTAPI_MOD_ATSC3);

    if (m_IsUsingAdvDemod)
    {
        // Attach to the advanced demodulation channel
        dr = m_AdvDemod.AttachToPort(&m_DtDvc, pHwf->m_Port);
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToAttachToChan, ::DtapiResult2Str(dr));
        HwCaps = m_AdvDemod.m_HwFuncDesc.m_Flags;
    } else {
        // Attach to the input channel
        dr = m_DtInp.AttachToPort(&m_DtDvc, pHwf->m_Port);
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailToAttachToChan, ::DtapiResult2Str(dr));
        HwCaps = m_DtInp.m_HwFuncDesc.m_Flags;
    }

    // Check for IQ sample monitor
    m_IqMonitor = (HwCaps&DTAPI_CAP_RX_IQ) != 0 && (HwCaps&DTAPI_CAP_VIRTUAL) != 0;

    // Check for demodulator card
    m_Demod = (HwCaps&DTAPI_CAP_DEMOD) !=0;

    // Check for IP output
    m_Ip =  (HwCaps&DTAPI_CAP_IP) !=0;

    // Check for LNB capabilities
    m_Lnb = (HwCaps&DTAPI_CAP_LNB) != 0;
}

// -.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::DisplayRecordInfo -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::DisplayRecordInfo()
{
    char sz[256];

    LogF("");
    LogF("Start recording");
    LogF("- Record file name      : %s", m_CmdLineParams.m_FileName);

    // Check for demodulator parameters
    if ( m_Demod )
    {
        LogF("- Modulation Type       : %s", m_CmdLineParams.ModType2Str() );
        if (m_CmdLineParams.m_CarrierFreq >= 0.0)
            LogF("- Carrier Frequency     : %.2f MHz", m_CmdLineParams.m_CarrierFreq);

        // QAM modulation parameters
        if (
            m_CmdLineParams.m_ModType==DTAPI_MOD_QAM4
            || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM16
            || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM32
            || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM64
            || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM128
            || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM256
            )
        {
            LogF("- J.83                  : %s", m_CmdLineParams.QamJ83Annex2Str() );
            if (m_CmdLineParams.m_SymbolRate == DTAPI_MOD_SYMRATE_AUTO)
                LogF("- Symbol rate           : auto");
            else
                LogF("- Symbol rate           : %d", m_CmdLineParams.m_SymbolRate);
        }
        // DVBS2 modulation parameters
        if (
            m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2
            || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_QPSK
            || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_8PSK
            || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_16APSK
            || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_32APSK
            || m_CmdLineParams.m_ModType == DTAPI_MOD_DVBS2_L3
            )
        {
            if (m_CmdLineParams.m_SymbolRate == DTAPI_MOD_SYMRATE_AUTO)
                LogF("- Symbol rate           : auto");
            else
                LogF("- Symbol rate           : %d", m_CmdLineParams.m_SymbolRate);
        }
        // DVB-T modulation parameters
        if ( m_CmdLineParams.m_ModType == DTAPI_MOD_DVBT )
            LogF("- Bandwidth             : %s", m_CmdLineParams.Bandwidth2Str() );

        // IQ parameters
        if ( m_CmdLineParams.m_ModType == DTAPI_MOD_IQDIRECT )
        {
            LogF("- IQ Demodulation type  : %s", m_CmdLineParams.IqDemodType2Str() );
            if (m_CmdLineParams.m_IqBandwidth != -1)
                LogF("- Bandwidth             : %d", m_CmdLineParams.m_IqBandwidth );
            LogF("- Sample rate           : %d", m_CmdLineParams.m_IqSampleRate );
        }

        // LNB and DiSEqC settings (DTA-2137 and DTA-2132)
        if (m_DtDvc.TypeNumber() == 2137 || m_DtDvc.TypeNumber() == 2132 || 
                                                             m_DtDvc.TypeNumber() == 2127)
        {
            LogF("- LNB Voltage           : %s", m_CmdLineParams.LnbVoltage2Str() );
            LogF("- LNB Tone              : %s", m_CmdLineParams.LnbTone2Str() );
            LogF("- LNB Burst             : %s", m_CmdLineParams.LnbBurst2Str());
            if (m_CmdLineParams.m_DiseqcPortGroup != -1 )
                LogF("- DiSEqC Port Group     : %02X (%s)", 
                                        m_CmdLineParams.m_DiseqcPortGroup,
                                        m_CmdLineParams.DiseqcPortGroup2Str() );
        }
    }

    // Do we have a IP input
    if ( m_Ip )
    {
        // Log IP parameters
        DtapiIpAddr2Str( sz, sizeof(sz), m_CmdLineParams.m_IpPars.m_Ip );
        LogF("- IP Address            : %s:%d", sz, m_CmdLineParams.m_IpPars.m_Port );
        LogF("- Protocol              : %s", m_CmdLineParams.IpProtocol2Str() );
    }
    
    if ( m_CmdLineParams.m_MaxSize <= 0 )
        LogF("- Maximum file size     : No maximum set");
    else if ( m_CmdLineParams.m_MaxSize < 1024 )
        LogF("- Maximum file size     : %.1f MB", double(m_CmdLineParams.m_MaxSize));
    else
        LogF("- Maximum file size     : %.1f GB", double(m_CmdLineParams.m_MaxSize/1024.0) );

    // Don't display RX-mode for ATSC 3.0, DAB and IQ-direct
    if (m_CmdLineParams.m_ModType!=DTAPI_MOD_ATSC3 
                                        && m_CmdLineParams.m_ModType!=DTAPI_MOD_DAB
                                        && m_CmdLineParams.m_ModType!=DTAPI_MOD_IQDIRECT)
        LogF("- Receive Mode          : %s", m_CmdLineParams.RxMode2Str() );
    
    if (m_CmdLineParams.m_RxMode == DTAPI_RXMODE_STL3 || 
                                      m_CmdLineParams.m_RxMode == DTAPI_RXMODE_STL3FULL ||
                                      m_CmdLineParams.m_RxMode == DTAPI_RXMODE_STL3ALL) 
        LogF("- L3 Frame timestamps   : %s", "Disabled");
    if (m_CmdLineParams.m_RxMode == (DTAPI_RXMODE_STL3 | DTAPI_RXMODE_TIMESTAMP32) || 
         m_CmdLineParams.m_RxMode == (DTAPI_RXMODE_STL3FULL | DTAPI_RXMODE_TIMESTAMP32) ||
         m_CmdLineParams.m_RxMode == (DTAPI_RXMODE_STL3ALL | DTAPI_RXMODE_TIMESTAMP32)) 
        LogF("- L3 Frame timestamps   : %s", "Enabled");

    if ( m_DtDvc.Category() == DTAPI_CAT_USB )
        sprintf(sz, "DTU-%d", m_DtDvc.TypeNumber() );
    else /*if ( m_DtDvc.Category() == DTAPI_CAT_PCI )*/
        sprintf(sz, "DTA-%d", m_DtDvc.TypeNumber() );
    LogF("- Input device          : %s port %d (#%d)", sz, m_CmdLineParams.m_Port, 
         m_CmdLineParams.m_DvcNum);
    
    __int64  Serial = m_DtDvc.m_DvcDesc.m_Serial;
    sprintf(sz, "%lld.%03lld.%03lld", Serial/1000000, (Serial%1000000)/1000, Serial%1000);
    LogF("- Serial                : %s", sz);

#ifdef WINBUILD
    Log("");
    Log("Press any key to stop recording");
    Log("");
#endif
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::InitInput -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
void Recorder::InitInput()
{
    DTAPI_RESULT dr;

    // Init channel to initial 'safe' state
    dr = m_DtInp.SetRxControl(DTAPI_RXCTRL_IDLE);   // Start in IDLE mode
    if ( dr != DTAPI_OK )
        throw Exc( c_ErrFailSetRxControl, ::DtapiResult2Str(dr) );
    dr = m_DtInp.ClearFifo();           // Clear FIFO (i.e. start with zero load)
    dr = m_DtInp.ClearFlags(0xFFFFFFFF);// Clear all flags

    // Apply IP settings (if we have a IP output)
    if ( m_Ip )
    {
        dr = m_DtInp.SetIpPars( &m_CmdLineParams.m_IpPars );
        if ( dr != DTAPI_OK )
            throw Exc( c_ErrFailSetIpPars, ::DtapiResult2Str(dr) );
    }

    if (m_CmdLineParams.m_ModType != DTAPI_MOD_IQDIRECT)
    {
        // First do a SetIoConfig to switch between ASI and SDI
        if ((m_CmdLineParams.m_RxMode&DTAPI_RXMODE_TS) != 0)
        {
            if ((m_DtInp.m_HwFuncDesc.m_Flags&DTAPI_CAP_ASI) != 0)
                dr = m_DtInp.SetIoConfig(DTAPI_IOCONFIG_IOSTD, DTAPI_IOCONFIG_ASI, -1);
        } else {
            // Set a dummy value for now, this will be changed to the correct IOCONFIG later
            dr = m_DtInp.SetIoConfig(DTAPI_IOCONFIG_IOSTD, DTAPI_IOCONFIG_SDI, 
                                                                   DTAPI_IOCONFIG_625I50);
        }

        if (dr != DTAPI_OK)
            throw Exc( c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr) );
    }

    // Set the polarity mode (for ASI interface only)
    if (   m_CmdLineParams.m_Polarity!=-1
        && (m_DtInp.m_HwFuncDesc.m_Flags&DTAPI_CAP_ASI)!=0
        && m_DtInp.m_HwFuncDesc.m_DvcDesc.m_TypeNumber!=225)
    {
        dr = m_DtInp.PolarityControl( m_CmdLineParams.m_Polarity );
        if ( dr != DTAPI_OK )
            throw Exc( c_ErrFailSetPolarCtrl, ::DtapiResult2Str(dr) );
    }

    // Apply de-modulation settings (if we have a de-modulator)
    if (m_Demod)
    {
        // Set a default for non-monitor IQ direct channels?
        if (m_CmdLineParams.m_CarrierFreq<0.0 && 
                            m_CmdLineParams.m_ModType==DTAPI_MOD_IQDIRECT && !m_IqMonitor)
            m_CmdLineParams.m_CarrierFreq = 578.0; // UHF-band

        // Set tuner frequency
        if (m_CmdLineParams.m_CarrierFreq >= 0.0)
        {
            __int64 Freq = (__int64)(m_CmdLineParams.m_CarrierFreq*1E6);
            dr = m_DtInp.SetTunerFrequency(Freq);
            if (dr != DTAPI_OK)
                throw Exc(c_ErrFailSetRfControl, ::DtapiResult2Str(dr));
        }

        bool IsDvbS = (  m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS_BPSK
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS_QPSK);

        bool IsDvbS2 = (  m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS2
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS2_QPSK
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS2_8PSK
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS2_16APSK
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_DVBS2_32APSK);

        bool IsQAM = (  m_CmdLineParams.m_ModType==DTAPI_MOD_QAM4
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM16
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM32
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM64
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM128
                          || m_CmdLineParams.m_ModType==DTAPI_MOD_QAM256);

        // Apply LNB and DiSEqC settings
        if (m_Lnb)
        {
            if (m_CmdLineParams.m_LnbVoltage != -1)
                dr = m_DtInp.LnbSetVoltage(m_CmdLineParams.m_LnbVoltage);

            if (m_CmdLineParams.m_LnbVoltage == -1 && !m_CmdLineParams.m_LnbToneEnable)
                dr = m_DtInp.LnbEnable(false);
            else
                dr = m_DtInp.LnbEnable(true);

            dr = m_DtInp.LnbEnableTone(m_CmdLineParams.m_LnbToneEnable);

            if (m_CmdLineParams.m_LnbBurst != -1)
                dr = m_DtInp.LnbSendBurst(m_CmdLineParams.m_LnbBurst);

            if (m_CmdLineParams.m_DiseqcPortGroup != -1)
            {
                dr = m_DtInp.LnbEnable(true);
                unsigned char  DiseqcStandByOffCmd[] = { 0xE0, 0x10, 0x03 };
                dr = m_DtInp.LnbSendDiseqcMessage(DiseqcStandByOffCmd,
                                                             sizeof(DiseqcStandByOffCmd));

#ifdef WINBUILD
                Sleep(100);
#else
                usleep(100000);
#endif 

                unsigned char  DiseqcPortGroupCmd[] = { 0xE0, 0x10, 0x038, 0x00 };
                DiseqcPortGroupCmd[3] = m_CmdLineParams.m_DiseqcPortGroup;
                dr = m_DtInp.LnbSendDiseqcMessage(DiseqcPortGroupCmd,
                                                              sizeof(DiseqcPortGroupCmd));
            }
        }

        // Set modulation control
        if (IsQAM)
        {
            // In case of QAM-A or QAM-C, the symbol rate must be set for the DTU-236
            if (  (    m_CmdLineParams.m_QamJ83Annex==DTAPI_MOD_J83_A 
                    || m_CmdLineParams.m_QamJ83Annex==DTAPI_MOD_J83_C )
                  && m_DtInp.m_HwFuncDesc.m_DvcDesc.m_TypeNumber==236
                  && m_CmdLineParams.m_SymbolRate==DTAPI_MOD_SYMRATE_AUTO)
                throw Exc( c_ErrSymbolRateNotSet );

            dr = m_DtInp.SetDemodControl(
                                m_CmdLineParams.m_ModType,
                                m_CmdLineParams.m_QamJ83Annex,
                                -1, m_CmdLineParams.m_SymbolRate);
        }
        else if (m_CmdLineParams.m_ModType == DTAPI_MOD_ATSC)
        {
            dr = m_DtInp.SetDemodControl( m_CmdLineParams.m_ModType,
                                          DTAPI_MOD_ATSC_VSB8, -1, 
                                          DTAPI_MOD_SYMRATE_AUTO);
        }
        else if (m_CmdLineParams.m_ModType==DTAPI_MOD_DAB)
        {
            // Set DAB demodcontrol
            DtDemodPars DemodPars;
            DemodPars.SetModType(DTAPI_MOD_DAB);
            dr = m_DtInp.SetDemodControl(&DemodPars);
            if (dr == DTAPI_OK)
            {
                // Select ETI (no parameters)
                DtDabEtiStreamSelPars DabSel;
                dr = m_DtInp.SetStreamSelection(DabSel);
            }
        }
        else if (m_CmdLineParams.m_ModType == DTAPI_MOD_DVBT)
        {
            // Set bandwidth an autop detect the rest
            int XtraPar1 = m_CmdLineParams.m_Bandwidth
                         | DTAPI_MOD_DVBT_CO_AUTO | DTAPI_MOD_DVBT_GU_AUTO 
                         | DTAPI_MOD_DVBT_IL_AUTO | DTAPI_MOD_DVBT_MD_AUTO;
            dr = m_DtInp.SetDemodControl( m_CmdLineParams.m_ModType,
                                          DTAPI_MOD_CR_AUTO, XtraPar1, -1);
        }
        else if (m_CmdLineParams.m_ModType==DTAPI_MOD_DVBT2)
        {
            // Set bandwidth and auto detect the rest
            DtDemodPars Pars;
            Pars.SetModType(DTAPI_MOD_DVBT2);
            DtDemodParsDvbT2* T2Pars = Pars.DvbT2();
            T2Pars->m_Bandwidth = m_CmdLineParams.m_Bandwidth;
            T2Pars->m_T2Profile = 0;
            dr = m_DtInp.SetDemodControl(&Pars);
        }
        else if (m_CmdLineParams.m_ModType==DTAPI_MOD_DVBC2)
        {
            // Set bandwidth and auto detect the rest
            DtDemodPars Pars;
            Pars.SetModType(DTAPI_MOD_DVBC2);
            DtDemodParsDvbC2* C2Pars = Pars.DvbC2();
            C2Pars->m_Bandwidth =  m_CmdLineParams.m_Bandwidth;
            dr = m_DtInp.SetDemodControl(&Pars);
        }
        else if (m_CmdLineParams.m_ModType==DTAPI_MOD_ISDBT)
        {
            // Set bandwidth and auto detect the rest
            DtDemodPars Pars;
            Pars.SetModType(DTAPI_MOD_ISDBT);
            DtDemodParsIsdbt* IsdbtPars = Pars.Isdbt();
            IsdbtPars->m_Bandwidth = m_CmdLineParams.m_Bandwidth;
            IsdbtPars->m_SubChannel = m_CmdLineParams.m_SubChannelNumber;
            IsdbtPars->m_NumberOfSegments = m_CmdLineParams.m_NumberOfSegments;
            dr = m_DtInp.SetDemodControl(&Pars);
        }
        else if (IsDvbS || IsDvbS2)
        {
            int  XtraPar1=-1;
            
            // For DVB-S2: Auto detect pilots and frame size
            if (IsDvbS2)
                XtraPar1 |= DTAPI_MOD_S2_PILOTS_AUTO | DTAPI_MOD_S2_FRM_AUTO;

            dr = m_DtInp.SetDemodControl( m_CmdLineParams.m_ModType,
                                          DTAPI_MOD_CR_AUTO, XtraPar1, 
                                          m_CmdLineParams.m_SymbolRate);
            if (dr != DTAPI_OK)
                    throw Exc(c_ErrFailSetDemodControl, ::DtapiResult2Str(dr));

            if (m_DtInp.HasCaps(DTAPI_CAP_RX_DVBS2_MIS) && IsDvbS2)
            {
                DtDvbS2StreamSelPars SelPars;
                SelPars.m_Isi = m_CmdLineParams.m_StreamId;
                dr = m_DtInp.SetStreamSelection(SelPars);
                if (dr != DTAPI_OK)
                    throw Exc(c_ErrFailSetDemodControl, ::DtapiResult2Str(dr));
            }
        }
        else if (m_CmdLineParams.m_ModType == DTAPI_MOD_IQDIRECT)
        {
            DtDemodPars  DemodPars;
            DemodPars.SetModType(m_CmdLineParams.m_ModType);
            DtDemodParsIq*  IqPars = DemodPars.Iq();
            IqPars->m_Bandwidth = m_CmdLineParams.m_IqBandwidth;
            IqPars->m_IqDemodType = m_CmdLineParams.m_IqDemodType;
            IqPars->m_SampleRate = m_CmdLineParams.m_IqSampleRate;
            dr = m_DtInp.SetDemodControl(&DemodPars);
        }
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailSetDemodControl, ::DtapiResult2Str(dr));
    }

    if (m_CmdLineParams.m_ModType != DTAPI_MOD_IQDIRECT)
    {
        // Set the receive mode
        dr = m_DtInp.SetRxMode(m_CmdLineParams.m_RxMode);
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailSetRxMode, ::DtapiResult2Str(dr));
    }
}

// -.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::InitAdvDemodInput -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::InitAdvDemodInput()
{
    DTAPI_RESULT dr;
    // Init channel to initial 'safe' state
    dr = m_AdvDemod.SetRxControl(DTAPI_RXCTRL_IDLE);   // Start in IDLE mode
    if (dr != DTAPI_OK)
        throw Exc(c_ErrFailSetRxControl, ::DtapiResult2Str(dr));
    dr = m_AdvDemod.ClearFlags(0xFFFFFFFF);// Clear all flags

    // Set tuner frequency
    if (m_CmdLineParams.m_CarrierFreq < 0.0)
        m_CmdLineParams.m_CarrierFreq = 578.0; // UHF-band
    __int64 Freq = (__int64)(m_CmdLineParams.m_CarrierFreq * 1E6);
    dr = m_AdvDemod.SetTunerFrequency(Freq);
    if (dr != DTAPI_OK)
        throw Exc(c_ErrFailSetRfControl, ::DtapiResult2Str(dr));

    // Apply de-modulation settings 
    if (m_CmdLineParams.m_ModType == DTAPI_MOD_ATSC3)
    {
        DtDemodPars DemodPars;
        DemodPars.SetModType(DTAPI_MOD_ATSC3);
        DtDemodParsAtsc3* pA3Pars = DemodPars.Atsc3();
        pA3Pars->m_Bandwidth = m_CmdLineParams.m_Bandwidth;
        dr = m_AdvDemod.SetDemodControl(&DemodPars);
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailSetDemodControl, ::DtapiResult2Str(dr));
        
        // Register data callback functions
        dr = m_AdvDemod.RegisterCallback(WriteAtsc3AlpFunc, this);
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailRegisterCallback, ::DtapiResult2Str(dr));
    }
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::Log -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::Log(const char* pMessage, bool IgnoreSilence)
{
    // Check for silent mode
    if ( !IgnoreSilence && m_CmdLineParams.m_SilentMode )
        return;
    // Print message and add new line
    printf("%s\n", pMessage);
}
// Overload with optional parameters
void Recorder::LogF(const char* pMessage, ... )
{
    char sz[512];

    va_list  ArgList;
    va_start(ArgList, pMessage);
    _vsnprintf(sz, sizeof(sz)-1, pMessage, ArgList);
    va_end(ArgList);

    Log(sz, false);
}

// .-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::LogMer -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::LogMer()
{
    DtStatistic Stat;
    Stat.SetId(DTAPI_STAT_MER);
    switch (m_CmdLineParams.m_ModType)
    {
    case DTAPI_MOD_QAM4:
    case DTAPI_MOD_QAM16:
    case DTAPI_MOD_QAM32:
    case DTAPI_MOD_QAM64:
    case DTAPI_MOD_QAM128:
    case DTAPI_MOD_QAM256:
    case DTAPI_MOD_ATSC:
    case DTAPI_MOD_DAB:
    case DTAPI_MOD_DVBT:
    case DTAPI_MOD_DVBS_QPSK:
    case DTAPI_MOD_DVBS_BPSK:
        // No PLPs or ISIs
        Stat.m_IdXtra[0] = -1;
        break;

    case DTAPI_MOD_ATSC3:
    case DTAPI_MOD_DVBT2:
    case DTAPI_MOD_DVBC2:
    case DTAPI_MOD_DVBS2:
    case DTAPI_MOD_DVBS2_QPSK:
    case DTAPI_MOD_DVBS2_8PSK:
    case DTAPI_MOD_DVBS2_16APSK:
    case DTAPI_MOD_DVBS2_32APSK:
    case DTAPI_MOD_ISDBT:
        // Always select PLP, Layer or ISI 0
        Stat.m_IdXtra[0] = 0;
        break;
    case DTAPI_MOD_IQDIRECT:
    default:
        return;
    }

    // Get and log MER
    DTAPI_RESULT dr;
    if (m_IsUsingAdvDemod)
      dr = m_AdvDemod.GetStatistics(1, &Stat);
    else
      dr = m_DtInp.GetStatistics(1, &Stat);
    if (dr == DTAPI_OK)
        LogF("MER: %g dB", Stat.m_ValueInt/10.0);
}

// -.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::WriteSdiHeaderToFile -.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
void Recorder::WriteSdiHeaderToFile(int VidStd, int NumFrames, int FrameSize)
{
    if (m_CmdLineParams.m_RecDtSdiFile)
    {
        // Write DT-SDI file header
        DtSdiFileHdrV2 DtSdiFileHdr;
        // Create header
        DtSdiFileHdr.m_BaseHdr.m_MagicCode1 = DTSDI_MAGIC_CODE1;
        DtSdiFileHdr.m_BaseHdr.m_MagicCode2 = DTSDI_MAGIC_CODE2;
        DtSdiFileHdr.m_BaseHdr.m_MagicCode3 = DTSDI_MAGIC_CODE3;
        DtSdiFileHdr.m_BaseHdr.m_FmtVersion = DTSDI_FMT_VERSION2;

        // Set data type in header
        DtSdiFileHdr.m_BaseHdr.m_DataType = VidStd2DtSdiDataType(VidStd);

        // Add flags
        DtSdiFileHdr.m_BaseHdr.m_Flags = 0;
        // FULL or active video mode?
        if ((m_CmdLineParams.m_RxMode&DTAPI_RXMODE_SDI_MASK) == DTAPI_RXMODE_SDI_FULL)
            DtSdiFileHdr.m_BaseHdr.m_Flags = DTSDI_SDI_FULL;
        else
            DtSdiFileHdr.m_BaseHdr.m_Flags = DTSDI_SDI_ACTVID;
        // 8-, 10- or 16-bit?
        if (0!=(m_CmdLineParams.m_RxMode & DTAPI_RXMODE_SDI_10B))
            DtSdiFileHdr.m_BaseHdr.m_Flags |= DTSDI_SDI_10B;
        else if (0!=(m_CmdLineParams.m_RxMode & DTAPI_RXMODE_SDI_16B))
            DtSdiFileHdr.m_BaseHdr.m_Flags |= DTSDI_SDI_16B;
        // Huffman compressed?
        if (DTAPI_RXMODE_SDI_HUFFMAN==(m_CmdLineParams.m_RxMode & DTAPI_RXMODE_SDI_HUFFMAN))
            DtSdiFileHdr.m_BaseHdr.m_Flags |= DTSDI_SDI_HUFFMAN;

        DtSdiFileHdr.m_FrameSize = FrameSize;
        DtSdiFileHdr.m_NumFrames = NumFrames;

        // Write header to file
        int NumBytesWritten = (int)fwrite(&DtSdiFileHdr, 1, sizeof(DtSdiFileHdr), m_pFile);
        // Sanity check
        if (NumBytesWritten != sizeof(DtSdiFileHdr))
            throw Exc(c_ErrFailWriteDtSdiFileHdr);
    }
    else if (m_CmdLineParams.m_RecSdiFile)
    {
        // Write SDI file header
        SdiFileHeader SdiHeader = SdiFileHeader{};
        DtVidStdInfo StdInfo{};
        DTAPI_RESULT Result = DtapiGetVidStdInfo(VidStd, StdInfo);
        if (Result != DTAPI_OK)
            throw Exc(c_ErrInvalidVideoStandard);

        int NumBits = 8;
        if (0!=(m_CmdLineParams.m_RxMode & DTAPI_RXMODE_SDI_10B))
            NumBits = 10;
        else if (0!=(m_CmdLineParams.m_RxMode & DTAPI_RXMODE_SDI_16B))
            NumBits = 16;

        SdiHeader.MagicCode = SDI_MAGIC;
        SdiHeader.Version = 0;
        SdiHeader.HeaderSize = 0; // Calculate later
        SdiHeader.NumPhysicalLinks = 1;
        if ((FrameSize%8) != 0)
            throw Exc(c_ErrInvalidAligment);
        SdiHeader.FrameSize = FrameSize;
        SdiHeader.NumFrames = NumFrames;
        SdiHeader.CompressionMode = SdiCompressionMode::NONE;

        // SDI format
        SdiFormat& Format = SdiHeader.Format;
        if (StdInfo.m_VidWidth == 720 && StdInfo.m_VidHeight == 576)
            Format.LineRate = SdiLineRate::SDI_SD;
        else if (StdInfo.m_VidWidth == 720 && StdInfo.m_VidHeight == 487)
            Format.LineRate = SdiLineRate::SDI_SD;
        else if (StdInfo.m_VidWidth == 1280 && StdInfo.m_VidHeight == 720)
            Format.LineRate = SdiLineRate::SDI_HD;
        else if (StdInfo.m_VidWidth == 1920 && StdInfo.m_VidHeight == 1080)
        {
            if (StdInfo.m_Fps < 50)
                Format.LineRate = SdiLineRate::SDI_HD;
            else
                Format.LineRate = SdiLineRate::SDI_3G;
        } else if (StdInfo.m_VidWidth == 3840 && StdInfo.m_VidHeight == 2160)
        {
            if (StdInfo.m_Fps < 50)
                Format.LineRate = SdiLineRate::SDI_6G;
            else
                Format.LineRate = SdiLineRate::SDI_12G;
        } else {
            throw Exc(c_ErrInvalidVideoStandard);
        }

        if (StdInfo.m_Is4k)
            Format.InterleavingType = SdiInterleavingType::SDI_2SI;
        else
            Format.InterleavingType = SdiInterleavingType::NONE;

        if (StdInfo.m_VidStd.m_StdCode == DTAPI_VIDSTD_1080P50B ||
            StdInfo.m_VidStd.m_StdCode == DTAPI_VIDSTD_1080P59_94B ||
            StdInfo.m_VidStd.m_StdCode == DTAPI_VIDSTD_1080P60B ||
            StdInfo.m_VidStd.m_StdCode == DTAPI_VIDSTD_2160P50B ||
            StdInfo.m_VidStd.m_StdCode == DTAPI_VIDSTD_2160P59_94B)
            Format.SdiLevel = SdiLevel::B_DL;
        else
            Format.SdiLevel = SdiLevel::A;

        // Logical frame properties
        LogicalFrameProperties& LfProps = SdiHeader.LogicalFrameProperties;
        LfProps.PictureRate.Num = (int)StdInfo.m_Fps;
        LfProps.PictureRate.Den = 1;
        if (StdInfo.m_IsFractional)
        {
            LfProps.PictureRate.Num = (LfProps.PictureRate.Num + 1) * 1000;
            LfProps.PictureRate.Den = 1001;
        }
        if (SdiHeader.Format.LineRate == SdiLineRate::SDI_SD)
            LfProps.AspectRatio = Fraction{ 4, 3 };
        else 
        {
            int64_t Div = std::gcd(StdInfo.m_VidWidth, StdInfo.m_VidHeight);
            LfProps.AspectRatio.Num = (int)(StdInfo.m_VidWidth / Div);
            LfProps.AspectRatio.Den = (int)(StdInfo.m_VidHeight / Div);
        }
        switch (StdInfo.m_VidStd.m_StdCode)
        {
        case DTAPI_VIDSTD_525I59_94:
        case DTAPI_VIDSTD_625I50:
        case DTAPI_VIDSTD_1080I50:
        case DTAPI_VIDSTD_1080I59_94:
        case DTAPI_VIDSTD_1080I60:
            LfProps.IsInterlaced = true;
            break;
        default:
            LfProps.IsInterlaced = false;
            break;
        };
        LfProps.SamplingStructure = SdiSamplingStructure::YCbCr422;
        LfProps.IsStereoscopic = 0;
        LfProps.BitDepth = NumBits;
        LfProps.PictureWidth = StdInfo.m_VidWidth;
        LfProps.PictureHeight = StdInfo.m_VidHeight;

        // Physical frame properties
        PhysicalFrameProperties& PfProps = SdiHeader.PhysicalFrameProperties;
        PfProps.NumFields = StdInfo.m_Field2StartLine > 0 ? 2 : 1;
        PfProps.CrcOmitted = 0; // NOTE(jst): just assume all DTSDI files have CRC
        PfProps.NumLinesFrame = StdInfo.m_NumLines;
        PfProps.NumSymsHanc = StdInfo.m_LineNumSymHanc + StdInfo.m_LineNumSymEav + StdInfo.m_LineNumSymSav;
        PfProps.NumSymsVancVideo = StdInfo.m_LineNumSymVanc;

        PfProps.FieldProperties[0].NumLinesField = (StdInfo.m_Field1EndLine - StdInfo.m_Field1StartLine) + 1;
        PfProps.FieldProperties[0].FirstVideoLine = StdInfo.m_Field1VidStartLine;
        PfProps.FieldProperties[0].NumLinesVideo = (StdInfo.m_Field1VidEndLine - StdInfo.m_Field1VidStartLine) + 1;
        if (PfProps.NumFields == 2)
        {
            PfProps.FieldProperties[1].NumLinesField = (StdInfo.m_Field2EndLine - StdInfo.m_Field2StartLine) + 1;
            PfProps.FieldProperties[1].FirstVideoLine = StdInfo.m_Field2VidStartLine;
            PfProps.FieldProperties[1].NumLinesVideo = (StdInfo.m_Field2VidEndLine - StdInfo.m_Field2VidStartLine) + 1;
        }
        std::vector<uint8_t> Buffer(sizeof(SdiFileHeader));
        int NumHeaderBits = WriteSdiFileHeader(Buffer.data(), (int)Buffer.size(), SdiHeader);
        SdiHeader.HeaderSize = ((NumHeaderBits + 63) / 64) * 8;     // 64-bit padding
        WriteSdiFileHeader(Buffer.data(), (int)Buffer.size(), SdiHeader);

        // Write header to file
        int NumBytesWritten = (int)fwrite(Buffer.data(), 1, SdiHeader.HeaderSize, m_pFile);
        // Sanity check
        if (NumBytesWritten != SdiHeader.HeaderSize)
            throw Exc(c_ErrFailWriteSdiFileHdr);
    }
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::RecordFile -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::RecordFile()
{
    DTAPI_RESULT dr;
    int VidStd=-1, SdiSubValue=-1;  //DTAPI_VIDSTD_UNKNOWN;
    bool ValidSignal = false, HwFifoOvf=false;
    int FifoLoad, NumToRead;
    bool RecordingSdi = m_CmdLineParams.m_RecDtSdiFile || m_CmdLineParams.m_RecSdiFile;

    m_NumBytesStored = 0;     // Number bytes stored in file
    m_NumFramesStored = 0;      // Number of frames stored in file
    m_FrameSize = -1;           // Frame size (for SDI files)

    //-.-.-.-.-.-.-.-.-.-.-.-.-.- First loop wait for a signal -.-.-.-.-.-.-.-.-.-.-.-.-.-
    while (!_kbhit() && !ValidSignal)
    {

#ifdef WINBUILD
        Sleep(500);         // Give the demodulator some time to tune and lock
#else
        usleep(500000);     // Give the demodulator some time to tune and lock
#endif 
        int CarrierDetect, n;

        if (m_Demod)
        {
            if (!m_IqMonitor)
            {
                bool RLock;
                // Check if demod has a valid signal
                dr = m_DtInp.GetStatistic(DTAPI_STAT_LOCK, RLock);
                if (dr != DTAPI_OK)
                    throw Exc(c_ErrFailGetStatistic, ::DtapiResult2Str(dr));
                if (!RLock)
                    continue;
            }
        }
        else if (RecordingSdi)
        {
            // Get signal status (including detected video standard)
            dr = m_DtInp.DetectIoStd(VidStd, SdiSubValue);
            if (dr!=DTAPI_OK && dr!=DTAPI_E_INVALID_VIDSTD && dr!=DTAPI_E_NO_VIDSTD)
                throw Exc(c_ErrFailDetectIoStd, ::DtapiResult2Str(dr));
            // Check if the video standard is SDI or HD-SDI
            if (VidStd!=DTAPI_IOCONFIG_SDI && VidStd!=DTAPI_IOCONFIG_HDSDI &&
                                                             VidStd!=DTAPI_IOCONFIG_3GSDI)
                continue;
            // Set correct IO config
            dr = m_DtInp.SetIoConfig(DTAPI_IOCONFIG_IOSTD, VidStd, SdiSubValue);
            if (dr != DTAPI_OK)
                throw Exc( c_ErrFailToSetIoConfig, ::DtapiResult2Str(dr) );
        } 
        else
        {
            dr = m_DtInp.GetStatus(n, n, CarrierDetect, n, n, n);
            // Check for valid carrier
            if (CarrierDetect != DTAPI_CLKDET_OK)
                continue;
        }
        ValidSignal = true;
    }
    if ( !ValidSignal )
        return; // No signal found i.e. user must have pressed a key to exit loop

    // For (DT)SDI mode we need to add a (dt)dtsdi file header
    if (RecordingSdi)
        WriteSdiHeaderToFile(SdiSubValue, 0, 0);

    // Init alignment
    if ( (m_CmdLineParams.m_RxMode&DTAPI_RXMODE_TS_MASK)==DTAPI_RXMODE_ST204 )
    {   
        m_Align = 204;      // Align on multiples of 204 bytes
        // Do we need to account for timestamps?
        if ( (m_CmdLineParams.m_RxMode & DTAPI_RXMODE_TIMESTAMP32)!=0 ) 
            m_Align += 4;
    }
    else if ( (m_CmdLineParams.m_RxMode&DTAPI_RXMODE_TS_MASK)==DTAPI_RXMODE_STRAW )
        m_Align = 4;        // Align on multiples of 4 bytes
    else if ( (m_CmdLineParams.m_RxMode&DTAPI_RXMODE_TS_MASK)==DTAPI_RXMODE_STTRP )
        m_Align = 212;      // Align on multiples of 212 bytes (TRP-packet + timestamp)
    else
    {
        m_Align = 188;      // Align on multiples of 188 bytes
        // Do we need to account for timestamps?
        if ( (m_CmdLineParams.m_RxMode & DTAPI_RXMODE_TIMESTAMP32)!=0 ) 
            m_Align += 4;
    }

    // Start reception
    dr = m_DtInp.SetRxControl(DTAPI_RXCTRL_RCV);
    if ( dr != DTAPI_OK )
        throw Exc(c_ErrFailSetRxControl, ::DtapiResult2Str(dr));

    //-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Second loop record data -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    auto MerLogTime = steady_clock::now();
    while (!_kbhit())
    {
        // Show MER once per second
        if (m_Demod && m_CmdLineParams.m_LogMer && (steady_clock::now()-MerLogTime) > 1s)
        {
            LogMer();
            MerLogTime = steady_clock::now();
        }

        // Get FIFO load. Continue if too few bytes available in receive FIFO
        dr = m_DtInp.GetFifoLoad(FifoLoad);
        if (FifoLoad < 2*1024*1024)
        {
            // Sleep 1ms, before checking load again
        #ifdef WINBUILD
            Sleep(1);
        #else
            usleep(1000);
        #endif 
            continue;
        }

        if (RecordingSdi)
        {
            // For SDI we use the read frame method, that automatically aligns on
            // frame boundaries
            NumToRead = c_BufSize;
            dr = m_DtInp.ReadFrame((unsigned int*)m_pBuf, NumToRead);
            m_NumFramesStored++;
            if (m_FrameSize == -1)
                m_FrameSize = NumToRead;
        }
        else
        {
            // Read aligned number of data from input channel
            NumToRead = (min(FifoLoad, c_BufSize) / m_Align) * m_Align;
            dr = m_DtInp.Read( m_pBuf, NumToRead );
        }
        if ( dr != DTAPI_OK )
            throw Exc(c_ErrFailRead, ::DtapiResult2Str(dr) );

        // Compute number of bytes left to store
        __int64 NumLeftToStore;
        if ( m_CmdLineParams.m_MaxSize <=0 )
            NumLeftToStore = NumToRead+1;   // No max => we can store all
        else
            NumLeftToStore = (m_CmdLineParams.m_MaxSize * 1024 * 1024) - m_NumBytesStored;

        // Compute number of bytes to write to file
        int NumToWrite=0;

        if (RecordingSdi)
        {
            // For SDI, data is aligned to frames => write all data
            NumToWrite = NumToRead;
        }
        else
        {
            // Account our alignment requirements
            NumLeftToStore = ((NumLeftToStore+m_Align-1) / m_Align) * m_Align;

            NumToWrite = ( NumLeftToStore >= NumToRead ) ? NumToRead : (int)NumLeftToStore;
        }

        // Write data to our file
        if ( NumToWrite != ::fwrite(m_pBuf, sizeof(char), NumToWrite, m_pFile) )
            throw Exc(c_ErrWriteFile);

        // Update number stored
        m_NumBytesStored += NumToWrite;
        //printf("\r%d MB written", m_NumBytesStored/(1024*1024));        // debug only
        
        // Should we stop?
        NumLeftToStore -= NumToWrite;
        if ( NumLeftToStore <= 0 )
            break;  // Reached max size => exit record loop

        // Check for overflow (only report overflow once)
        int Flags=0, Latched=0;
        dr = m_DtInp.GetFlags(Flags, Latched);
        if ( 0!=(Latched & DTAPI_RX_FIFO_OVF) && !HwFifoOvf )
        {
            LogF(c_WarnHwFifoOverflow);
            HwFifoOvf = true;   // Only log warning once
        }
        dr = m_DtInp.ClearFlags(Latched);
    }
    // Update file header for SDI
    if (RecordingSdi)
    {
        ::fseek(m_pFile, 0, SEEK_SET);
        WriteSdiHeaderToFile(SdiSubValue, m_NumFramesStored, m_FrameSize);
    }
}

// .-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::RecordAdvDemodFile -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
void Recorder::RecordAdvDemodFile()
{
    DTAPI_RESULT dr;
    bool ValidSignal = false, HwFifoOvf=false;
    m_NumBytesStored = 0;     // Clear number bytes stored in file

    //-.-.-.-.-.-.-.-.-.-.-.-.-.- First loop wait for a signal -.-.-.-.-.-.-.-.-.-.-.-.-.-
    while (!_kbhit() && !ValidSignal)
    {
#ifdef WINBUILD
        Sleep(500);         // Give the demodulator some time to tune and lock
#else
        usleep(500000);     // Give the demodulator some time to tune and lock
#endif 
        // Get signal lock
        DtStatistic Stats[1];
        Stats[0].SetId(DTAPI_STAT_LOCK);
        dr = m_AdvDemod.GetStatistics(1, Stats);
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailGetStatistic, ::DtapiResult2Str(dr));

        // Check Signal lock
        bool SignalLock;
        Stats[0].GetValue(SignalLock);
        if (Stats[0].m_Result == DTAPI_OK && SignalLock)
        {
            // Valid input signal detected
            ValidSignal = true;
        }
    }
    if (!ValidSignal)
        return; // No signal found i.e. user must have pressed a key to exit loop

    // For ATSC 3.0 select all PLPs and start the record file with a PCAP file header
    if (m_CmdLineParams.m_ModType == DTAPI_MOD_ATSC3)
    {
       // Get ATSC 3.0 L1-data
        DtStatistic Stats[1];
        Stats[0].SetId(DTAPI_STAT_ATSC3_L1DATA);
        dr = m_AdvDemod.GetStatistics(1, Stats);
        if (dr != DTAPI_OK)
            throw Exc(c_ErrFailGetStatistic, ::DtapiResult2Str(dr));
        if (Stats[0].m_Result != DTAPI_OK)
            throw Exc(c_ErrFailGetStatistic, ::DtapiResult2Str(Stats[0].m_Result));
        DtAtsc3DemodL1Data* pL1Data;
        Stats[0].GetValue(pL1Data);

        // Select the all PLP's stream
        for (int sf = 0; sf<(int)pL1Data->m_L1Detail.m_Subframes.size(); sf++)
        {
            for (int p = 0; p<(int)pL1Data->m_L1Detail.m_Subframes[sf].m_Plps.size(); p++)
            {
                int PlpId = pL1Data->m_L1Detail.m_Subframes[sf].m_Plps[p].m_Id;
                DtAtsc3StreamSelPars  A3StreamSel;
                A3StreamSel.m_PlpId = PlpId;
                DtStreamSelPars StreamSel;
                StreamSel.m_Id = PlpId;
                StreamSel.m_StreamType = STREAM_ATSC3;
                StreamSel.u.m_Atsc3 = A3StreamSel;
                dr = m_AdvDemod.OpenStream(StreamSel);
                if (dr != DTAPI_OK)
                    throw Exc(c_ErrFailOpenStream, ::DtapiResult2Str(dr));
            }
        }

        // Write PCAP file header
        PcapFileHeader  FileHdr;
        FileHdr.m_MagicNumber = PCAP_MAGIC_NUMBER_US;
        FileHdr.m_VersionMajor = PCAP_VERSION_MAJOR;
        FileHdr.m_VersionMinor = PCAP_VERSION_MINOR;
        FileHdr.m_ThisZone = 0;
        FileHdr.m_SigFigs = 0;
        FileHdr.m_SnapLen = 0xFFFF;
        FileHdr.m_Network = 101;    //Raw IP
        ::fwrite(&FileHdr, 1, sizeof(FileHdr), m_pFile);
    }

    // Start reception
    dr = m_AdvDemod.SetRxControl(DTAPI_RXCTRL_RCV);
    if (dr != DTAPI_OK)
        throw Exc(c_ErrFailSetRxControl, ::DtapiResult2Str(dr));

    //-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Second loop record data -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    auto MerLogTime = steady_clock::now();
    while (!_kbhit())
    {
        // Sleep 10ms
        #ifdef WINBUILD
            Sleep(10);
        #else
            usleep(10000);
        #endif 

        // Show MER once per second
        if (m_CmdLineParams.m_LogMer && (steady_clock::now() - MerLogTime) > 1s)
        {
            LogMer();
            MerLogTime = steady_clock::now();
        }
        // Check for overflow (only report overflow once)
        int Flags=0, Latched=0;
        dr = m_AdvDemod.GetFlags(Flags, Latched);
        if ( 0!=(Latched & DTAPI_RX_FIFO_OVF) && !HwFifoOvf )
        {
            LogF(c_WarnHwFifoOverflow);
            HwFifoOvf = true;   // Only log warning once
        }
        dr = m_AdvDemod.ClearFlags(Latched);

        // Is a maximum specified and must we stop?
        if (m_CmdLineParams.m_MaxSize>0 && 
                                    m_NumBytesStored>m_CmdLineParams.m_MaxSize* 1024*1024)
            break;  // Reached max size => exit record loop
    }
    // Stop reception
    dr = m_AdvDemod.SetRxControl(DTAPI_RXCTRL_IDLE);
    if ( dr != DTAPI_OK )
        throw Exc(c_ErrFailSetRxControl, ::DtapiResult2Str(dr));
}

// -.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::WriteAtsc3AlpFunc -.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
// Callback, writing ALP-packets to file using RAW-IP format.
//
void Recorder::WriteAtsc3AlpFunc(void * pOpaque, DtStreamSelPars & StreamSel,
                                                   const unsigned char* pData, int Length)
{
    unsigned PlpId = (unsigned)StreamSel.m_Id;
    Recorder* pThis = (Recorder*)pOpaque;

    // This function must be fast, since it is executed in the demodulator thread!
    PcapPckHeader PacketHdr;
    GetTimestamp(PacketHdr.m_Seconds, PacketHdr.m_MicroOrNanoSeconds);
    PacketHdr.m_InclLen = PacketHdr.m_OrigLen = Length + sizeof(IPHeader)
        + sizeof(UDPHeader);
    fwrite(&PacketHdr, 1, sizeof(PacketHdr), pThis->m_pFile);

    // Write dummy IP and UDP-header
    uint8_t* pUdpHdr = (uint8_t*)pThis->m_pBuf;
    memcpy(pUdpHdr+sizeof(IPHeader) + sizeof(UDPHeader), pData, Length);
    NwUtils::add_udp_headers2(pUdpHdr, Length,
        (192 << 24) | (168 << 16) | (0 << 8) | 100,
        60001,
        (240 << 24) | (0 << 16) | (0 << 8) | PlpId,
        60002);
    fwrite(pUdpHdr, 1, sizeof(IPHeader) + sizeof(UDPHeader) + Length, pThis->m_pFile);

    // Update number of bytes stored (count only payload)
    pThis->m_NumBytesStored += Length;
}

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::Record -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
int Recorder::Record(int argc, char* argv[])
{
    int RetValue=0;
    try
    {
        try
        {
            // Parse command line options
            m_CmdLineParams.Init();
            m_CmdLineParams.ParseCommandLine(argc, argv);
        }
        catch ( Exc e ) {
            Log(e, true);
            Log("", true);
            Log("Use -? option for help", true);

            return -1;
        }
        catch ( ... ) {
            Log("Unknown exception encountered. Exiting application", true);
            return -1;
        }
        if ( m_CmdLineParams.m_ShowHelp )
        {
            // Disable silent mode
            m_CmdLineParams.m_SilentMode = false;
            ShowHelp();
            return RetValue;
        }

        //-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Print start message -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

        LogF("DtRecord - Recording Utility V%d.%d.%d (c) 2000-2025 DekTec Digital Video B.V.\n",
             DTRECORD_VERSION_MAJOR, DTRECORD_VERSION_MINOR, DTRECORD_VERSION_BUGFIX );

        //-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Open the record file -.-.-.-.-.-.-.-.-.-.-.-.-.-.-

        // Try to open the file for writing
        m_pFile = fopen( m_CmdLineParams.m_FileName, "wb");
        if ( m_pFile == NULL )
            throw Exc(c_ErrFailToOpenFile, m_CmdLineParams.m_FileName);

        //-.-.-.-.-.-.-.-.-.-.-.-.- Attach to the output channel -.-.-.-.-.-.-.-.-.-.-.-.-

        AttachToInput();

        //-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Final initialization -.-.-.-.-.-.-.-.-.-.-.-.-.-.-

        // Init input
        if (m_IsUsingAdvDemod)
            InitAdvDemodInput();
        else
            InitInput();

        // Create our transfer buffer
        m_pBuf = new char [c_BufSize];

        // -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Start recording -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-

        // Print start info
        DisplayRecordInfo();

        // Record file
        if (m_IsUsingAdvDemod)
            RecordAdvDemodFile();
        else
            RecordFile();
    }
    catch (Exc e)
    {
        Log(e, true);
        RetValue = -1;
    }

    // Detach from channel and device
    if (m_IsUsingAdvDemod)
    {
        m_AdvDemod.SetRxControl(DTAPI_RXCTRL_IDLE);
        m_AdvDemod.Detach(DTAPI_INSTANT_DETACH);
    } else {
        m_DtInp.SetRxControl(DTAPI_RXCTRL_IDLE);
        m_DtInp.Detach(DTAPI_INSTANT_DETACH);
    }
    m_DtDvc.Detach();

    // Free our buffer
    if (m_pBuf != NULL) {
        delete[] m_pBuf;
        m_pBuf = NULL;
    }

    // do not forget to close our file
    if (m_pFile != NULL) {
        ::fclose(m_pFile);
        m_pFile = NULL;
    }

    return RetValue;
}

//.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- Recorder::ShowHelp -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
//
void Recorder::ShowHelp()
{
    Log("" );
    Log("Usage:");
    Log( "   DtRecord recfile [-t type] [-n number] [-i port] [-x maxsize]\n" \
         "          [-m mode] [-c compression] \n" \
         "          [-mt type] [-mf freq] [-ma annex] [-mb bandwidth]\n" \
         "          [-ms symbolrate] [-mn segments] [-mc subchannel] [-mi identifier]\n" \
         "          [-lnbv voltage] [-lnbt] [-diseqcpg port_group_data_byte]\n" \
         "          [-ipa ip_address_pair] [-ipp protocol] [-pc polarity]\n" \
         "          [-iqd iq_demodtype] [-iqb iq_bandwidth] [-iqs samplerate] [-s] [-?]");
    Log("");
    Log("Where:");
    Log("   recfile is the name of the record file");
    Log("");
    Log("NOTE: It is recommended to use the '.sdi' extension for SDI record files\n"
        "      and the '.dtsdi' extension for legacy DTSDI record files");
    Log("");
    Log("Options:");
    Log("   -t  Device type to use (default: any input device)");
    Log("         Use: 112  (for DTA-112)");
    Log("              115  (for DTA-115)");
    Log("              116  (for DTA-116)");
    Log("              117  (for DTA-117)");
    Log("              122  (for DTA-122)");
    Log("              124  (for DTA-124)");
    Log("              140  (for DTA-140)");
    Log("              145  (for DTA-145)");
    Log("              160  (for DTA-160)");
    Log("              2127 (for DTA-2127)");
    Log("              2131 (for DTA-2131)");
    Log("              2132 (for DTA-2132)");
    Log("              2135 (for DTA-2135)");
    Log("              2136 (for DTA-2136)");
    Log("              2137 (for DTA-2137)");
    Log("              2138 (for DTA-2138)");
    Log("              2139 (for DTA-2139)");
    Log("              2144 (for DTA-2144)");
    Log("              2145 (for DTA-2145)");
    Log("              2152 (for DTA-2152)");
    Log("              2154 (for DTA-2154)");
    Log("              2160 (for DTA-2160)");
    Log("              2172 (for DTA-2172)");
    Log("              2174 (for DTA-2174)");
    Log("              2175 (for DTA-2175)");
    Log("              2178 (for DTA-2178)");
    Log("              2179 (for DTA-2179)");
    Log("              2195 (for DTA-2195)");
    Log("              225  (for DTU-225)");
    Log("              235  (for DTU-235)");
    Log("              245  (for DTU-245)");
    Log("              331  (for DTU-331)");
    Log("              351  (for DTU-351)");
    Log("              545  (for DTA-545)");
    Log("   -n  Device number to use (default: 1)");
    Log("   -i  Port number of the input channel to use (default: 1) ");
    Log("   -m  Receive Mode (default: ST188)");
    Log("         Use: ST188         Store packets as 188-byte packets");
    Log("              ST204         Store packets as 204-byte packets");
    Log("              STMP2         Store packets as 188 or 204 without modification");
    Log("              STRAW         No notion of packets. Store all valid bytes");
    Log("              STL3          Store DVB-S2 baseband frames (requires -mt DVBS2)");
    Log("              STL3TS        Store DVB-S2 baseband frames + timestamp format (requires -mt DVBS2");
    Log("              STL3FULL      Store DVB-S2 baseband frames with 0 frames(requires -mt DVBS2)");
    Log("              STL3FULLTS    Store DVB-S2 baseband frames with 0 frames + timestamp format (requires -mt DVBS2");
    Log("              STTRP         Store in transparent-mode-packet + timestamp format");
    Log("              ST188T        Store packets as 188-byte packets + timestamp");
    Log("              ST204T        Store packets as 204-byte packets + timestamp");
    Log("              RAWASI        Store RAW ASI packets (270Mbit/s)");
    Log("              SDI10B        Store complete SDI frame, in 10-bit SDI file format");
    Log("              SDI16B        Store complete SDI frame, in 16-bit SDI file format");
    Log("              DTSDI10B      Store complete SDI frame, in 10-bit legacy DTSDI file format");
    Log("              DTSDI16B      Store complete SDI frame, in 16-bit legacy DTSDI file format");
    Log("   -c  Huffman compression (default: OFF)");
    Log("         Use: OFF  Disable Huffman compression");
    Log("              ON   Enable Huffman compression");
    Log("   NOTE: Huffman compression can only be used with SDI modes");
    Log("   -mt  Modulation type (default: QAM64)");
    Log("         Use:  QAM64  (for QAM-64 modulation)");
    Log("               QAM256 (for QAM-256 modulation)");
    Log("               ATSC   (for ATSC (8-VSB) modulation)");
    Log("               ATSC3  (for ATSC 3.0 modulation)");
    Log("               DAB    (for DAB(+) modulation)");
    Log("               DVBT   (for DVB-T (COFDM) modulation)");
    Log("               DVBT2  (for DVB-T2 modulation)");
    Log("               DVBC2  (for DVB-C2 modulation)");
    Log("               DVBS   (for DVB-S (QPSK or BSPSK) modulation)");
    Log("               DVBS2  (for DVB-S2 modulation)");
    Log("               DVBS2_QPSK    (for DVB-S2 QPSK modulation)");
    Log("               DVBS2_8PSK    (for DVB-S2 8PSK modulation)");
    Log("               DVBS2_16APSK  (for DVB-S2 16APSK modulation)");
    Log("               DVBS2_32APSK  (for DVB-S2 32APSK modulation)");
    Log("               IQ     (for IQ samples)");
    Log("               ISDBT  (for ISDB-T modulation)");
    Log("   -mf  Modulation carrier frequency in MHz (default: 578.0MHz)");
    Log("   -ma  J.83 Annex (default: Annex A");
    Log("         Use:  A   (for J.83 annex A (DVB-C))");
    Log("               B   (for J.83 annex B ('American QAM')");
    Log("               C   (for J.83 annex C ('Japanese QAM')");
    Log("   -mb  DVB-T bandwidth (default: 8MHz)");
    Log("         Use:  6   (for 6MHz)");
    Log("               7   (for 7MHz)");
    Log("               8   (for 8MHz)");
    Log("   -ms  Symbol rate for J.83 Annex * and DVBS2 (default: autodetect)");
    Log("   -mn  ISDB-T Number of Segments; use 1,3 or 13 (default: 13)");
    Log("   -mc  ISDB-T Sub channel number, 0..41 (default 22)");
    Log("   -mi  DVBS2 Stream identifier, 0..255 (default 0)");
    Log("   -mer Log the MER of the modulated signal (default: no MER logging)");
    Log("   -lnbv  LNB voltage (default: disabled)");
    Log("         Use:  13   (for 13V)");
    Log("               14   (for 14V)");
    Log("               18   (for 18V)");
    Log("               19   (for 19V)");
    Log("   -lnbt  LNB 22kHz tone (default: no tone)");
    Log("   -lnbb  LNB send burst (use A or B, default: no burst)");
    Log("   -diseqcpg  issue DiSEqC port group command");
    Log("          Use: F0 => OptA, PosA, V, Lo");
    Log("               F1 => OptA, PosA, V, Hi");
    Log("               F2 => OptA, PosA, H, Lo");
    Log("               F3 => OptA, PosA, H, Hi");
    Log("               F4 => OptA, PosB, V, Lo");
    Log("               F5 => OptA, PosB, V, Hi");
    Log("               F6 => OptA, PosB, H, Lo");
    Log("               F7 => OptA, PosB, H, Hi");
    Log("               F8 => OptB, PosA, V, Lo");
    Log("               F9 => OptB, PosA, V, Hi");
    Log("               FA => OptB, PosA, H, Lo");
    Log("               FB => OptB, PosA, H, Hi");
    Log("               FC => OptB, PosB, V, Lo");
    Log("               FD => OptB, PosB, V, Hi");
    Log("               FE => OptB, PosB, H, Lo");
    Log("               FF => OptB, PosB, H, Hi");
    Log("   -ipa IPv4 address/port (e.g. 192.168.0.1[:5768], port is optional");
    Log("        IPv6 address/port (e.g. 'fe80::18d6:e0ed:def1:1805'[:5768], port is optional");
    Log("   -ipp IP Protocol (default: AUTO)");
    Log("         Use:  AUTO (for AUTO detect)");
    Log("               UPD  (for UDP)");
    Log("               RTP  (for RTP)");
    Log("   -pc  Polarity Control (default: AUTO; in raw-mode: NORMAL )");
    Log("         Use:  AUTO   (for AUTO detect)");
    Log("               NORMAL (for Normal mode)");
    Log("               INVERT (for Inverted mode)");
    Log("   -iqd IQ demodulation type (default: QAM)");
    Log("         Use:  QAM (for QAM)");
    Log("               OFDM (for OFDM)");
    Log("   -iqb IQ bandwith (in Hz)");
    Log("   -iqs IQ sample rate (in Hz)");
    Log("   -s  Silent mode. No messages printed");
    Log("   -?  Display this help");
    Log("");
    Log("NOTE: The first option can only be the recfile or the help option");
    Log("");
    Log("Examples:");
    Log("   DtRecord myfile.ts -x 10");
    Log("   DtRecord myfile.ts -t 124 -i 2 -m ST204");
    Log("   DtRecord mysdifile.sdi -t 2172 -m SDI10B");
    Log("   DtRecord mysdifile.dtsdi -t 2172 -m DTSDI10B");
    Log("Example recording the first TS in a MIS DVBS2:");
    Log("   DtRecord DVBS2.ts -t 2137 -i 1 -mt DVBS2_QPSK -mf 950 -m ST188");
    Log("Example recording a TS with identifier 1 in a MIS DVBS2:");
    Log("   DtRecord DVBS2.ts -t 2132 -i 1 -mi 1 -mt DVBS2_QPSK -mf 950 -m ST188");
    Log("Example recording ATSC 3.0 PCAP format");
    Log("   DtRecord Atsc3Alp.pcap -t 2131 -i 1 -mt ATSC3 -mf 500");
    Log("Example recording DAB(+) ETI format");
    Log("   DtRecord Dab.eti -t 2131 -i 1 -mt DAB -mf 500");
    Log("Examples recording L3 baseband frames:");
    Log("   DtRecord DVBS2.l3 -t 2137 -i 1 -mt DVBS2_QPSK -mf 950 -m STL3");
    Log("   DtRecord DVBS2.l3 -t 2137 -i 1 -mt DVBS2_QPSK -mf 950 -m STL3TS");
    Log("Example recording IQ samples without tuning:");
    Log("   DtRecord IQSamples.iq -t 2132 -i 2 -mt IQ -iqs 10000000");
    Log("Example recording IQ samples:");
    Log("   DtRecord IQSamples.iq -t 2131 -i 1 -mt IQ -iqb 8000000 -iqs 10000000 -mf 500");
    Log("");
    LogF("DtRecord version: %d.%d.%d\n", DTRECORD_VERSION_MAJOR, DTRECORD_VERSION_MINOR,
         DTRECORD_VERSION_BUGFIX);
}

//+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ Application entry point +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=

//-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.- main -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
//
int main(int argc, char* argv[])
{
    int RetValue(0);
    Recorder TheRecorder;
    try
    {
        RetValue = TheRecorder.Record(argc, argv);
    }
    catch(...)
    {
        RetValue = -1;
    }
    return RetValue;
}
